--- /dev/null
+// OpenTween - Client of Twitter
+// Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System.Net;
+using System.Net.Http;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using OpenTween.Api;
+using Xunit;
+
+namespace OpenTween.Connection
+{
+ public class ApiResponseTest
+ {
+ [Fact]
+ public async Task ReadAsBytes_Test()
+ {
+ using var responseContent = new ByteArrayContent(new byte[] { 1, 2, 3 });
+ using var responseMessage = new HttpResponseMessage
+ {
+ StatusCode = HttpStatusCode.OK,
+ Content = responseContent,
+ };
+ using var response = new ApiResponse(responseMessage);
+
+ Assert.Equal(new byte[] { 1, 2, 3 }, await response.ReadAsBytes());
+ }
+
+ [DataContract]
+ public struct TestJson
+ {
+ [DataMember(Name = "foo")]
+ public int Foo { get; set; }
+ }
+
+ [Fact]
+ public async Task ReadAsJson_Test()
+ {
+ using var responseContent = new StringContent("""{"foo":123}""");
+ using var responseMessage = new HttpResponseMessage
+ {
+ StatusCode = HttpStatusCode.OK,
+ Content = responseContent,
+ };
+ using var response = new ApiResponse(responseMessage);
+
+ Assert.Equal(new() { Foo = 123 }, await response.ReadAsJson<TestJson>());
+ }
+
+ [Fact]
+ public async Task ReadAsJson_InvalidJsonTest()
+ {
+ using var responseContent = new StringContent("### Invalid JSON Response ###");
+ using var responseMessage = new HttpResponseMessage
+ {
+ StatusCode = HttpStatusCode.OK,
+ Content = responseContent,
+ };
+ using var response = new ApiResponse(responseMessage);
+
+ var ex = await Assert.ThrowsAsync<TwitterApiException>(
+ () => response.ReadAsJson<TestJson>()
+ );
+ Assert.Equal("### Invalid JSON Response ###", ex.ResponseText);
+ }
+
+ [Fact]
+ public async Task ReadAsJsonXml_Test()
+ {
+ using var responseContent = new StringContent("""{"foo":123}""");
+ using var responseMessage = new HttpResponseMessage
+ {
+ StatusCode = HttpStatusCode.OK,
+ Content = responseContent,
+ };
+ using var response = new ApiResponse(responseMessage);
+
+ var rootElm = await response.ReadAsJsonXml();
+ var xmlString = rootElm.ToString(SaveOptions.DisableFormatting);
+ Assert.Equal("""<root type="object"><foo type="number">123</foo></root>""", xmlString);
+ }
+
+ [Fact]
+ public async Task ReadAsJsonXml_InvalidJsonTest()
+ {
+ using var responseContent = new StringContent("### Invalid JSON Response ###");
+ using var responseMessage = new HttpResponseMessage
+ {
+ StatusCode = HttpStatusCode.OK,
+ Content = responseContent,
+ };
+ using var response = new ApiResponse(responseMessage);
+
+ var ex = await Assert.ThrowsAsync<TwitterApiException>(
+ () => response.ReadAsJsonXml()
+ );
+ Assert.Equal("### Invalid JSON Response ###", ex.ResponseText);
+ }
+ }
+}
--- /dev/null
+// OpenTween - Client of Twitter
+// Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using Xunit;
+
+namespace OpenTween.Connection
+{
+ public class GetRequestTest
+ {
+ [Fact]
+ public void CreateMessage_Test()
+ {
+ var request = new GetRequest
+ {
+ RequestUri = new("statuses/show.json", UriKind.Relative),
+ Query = new Dictionary<string, string>
+ {
+ ["id"] = "12345",
+ },
+ };
+
+ var baseUri = new Uri("https://api.twitter.com/v1/");
+ using var requestMessage = request.CreateMessage(baseUri);
+
+ Assert.Equal(HttpMethod.Get, requestMessage.Method);
+ Assert.Equal(new("https://api.twitter.com/v1/statuses/show.json?id=12345"), requestMessage.RequestUri);
+ }
+
+ [Fact]
+ public void BuildUriWithQuery_Test()
+ {
+ var uri = new Uri("https://example.com/hoge");
+ var query = new Dictionary<string, string>
+ {
+ ["foo"] = "bar",
+ };
+ Assert.Equal(new("https://example.com/hoge?foo=bar"), GetRequest.BuildUriWithQuery(uri, query));
+ }
+
+ [Fact]
+ public void BuildUriWithQuery_NullTest()
+ {
+ var uri = new Uri("https://example.com/hoge");
+ Assert.Equal(new("https://example.com/hoge"), GetRequest.BuildUriWithQuery(uri, null));
+ }
+
+ [Fact]
+ public void BuildUriWithQuery_CannotMergeTest()
+ {
+ var uri = new Uri("https://example.com/hoge?aaa=111");
+ var query = new Dictionary<string, string>
+ {
+ ["bbb"] = "222",
+ };
+ Assert.Throws<NotSupportedException>(
+ () => GetRequest.BuildUriWithQuery(uri, query)
+ );
+ }
+ }
+}
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Moq;
}
[Fact]
- public async Task GetAsync_UpdateRateLimitTest()
+ public async Task SendAsync_Test()
+ {
+ using var mockHandler = new HttpMessageHandlerMock();
+ using var http = new HttpClient(mockHandler);
+ using var apiConnection = new TwitterApiConnection(ApiKey.Create(""), ApiKey.Create(""), "", "");
+ apiConnection.Http = http;
+
+ mockHandler.Enqueue(x =>
+ {
+ Assert.Equal(HttpMethod.Get, x.Method);
+ Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
+ x.RequestUri.GetLeftPart(UriPartial.Path));
+
+ var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
+
+ Assert.Equal("1111", query["aaaa"]);
+ Assert.Equal("2222", query["bbbb"]);
+
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent("\"hogehoge\""),
+ };
+ });
+
+ var request = new GetRequest
+ {
+ RequestUri = new("hoge/tetete.json", UriKind.Relative),
+ Query = new Dictionary<string, string>
+ {
+ ["aaaa"] = "1111",
+ ["bbbb"] = "2222",
+ },
+ EndpointName = "/hoge/tetete",
+ };
+
+ using var response = await apiConnection.SendAsync(request);
+
+ Assert.Equal("hogehoge", await response.ReadAsJson<string>());
+
+ Assert.Equal(0, mockHandler.QueueCount);
+ }
+
+ [Fact]
+ public async Task SendAsync_UpdateRateLimitTest()
{
using var mockHandler = new HttpMessageHandlerMock();
using var http = new HttpClient(mockHandler);
{
Headers =
{
- { "X-Rate-Limit-Limit", "150" },
- { "X-Rate-Limit-Remaining", "100" },
- { "X-Rate-Limit-Reset", "1356998400" },
- { "X-Access-Level", "read-write-directmessages" },
+ { "X-Rate-Limit-Limit", "150" },
+ { "X-Rate-Limit-Remaining", "100" },
+ { "X-Rate-Limit-Reset", "1356998400" },
+ { "X-Access-Level", "read-write-directmessages" },
},
Content = new StringContent("\"hogehoge\""),
};
var apiStatus = new TwitterApiStatus();
MyCommon.TwitterApiInfo = apiStatus;
- var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
+ var request = new GetRequest
+ {
+ RequestUri = new("hoge/tetete.json", UriKind.Relative),
+ EndpointName = "/hoge/tetete",
+ };
- await apiConnection.GetAsync<string>(endpoint, null, endpointName: "/hoge/tetete");
+ using var response = await apiConnection.SendAsync(request);
Assert.Equal(TwitterApiAccessLevel.ReadWriteAndDirectMessage, apiStatus.AccessLevel);
Assert.Equal(new ApiLimit(150, 100, new DateTimeUtc(2013, 1, 1, 0, 0, 0)), apiStatus.AccessLimit["/hoge/tetete"]);
}
[Fact]
- public async Task GetAsync_ErrorStatusTest()
+ public async Task SendAsync_ErrorStatusTest()
{
using var mockHandler = new HttpMessageHandlerMock();
using var http = new HttpClient(mockHandler);
};
});
- var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
+ var request = new GetRequest
+ {
+ RequestUri = new("hoge/tetete.json", UriKind.Relative),
+ };
var exception = await Assert.ThrowsAsync<TwitterApiException>(
- () => apiConnection.GetAsync<string>(endpoint, null, endpointName: "/hoge/tetete")
+ () => apiConnection.SendAsync(request)
);
// エラーレスポンスの読み込みに失敗した場合はステータスコードをそのままメッセージに使用する
}
[Fact]
- public async Task GetAsync_ErrorJsonTest()
+ public async Task SendAsync_ErrorJsonTest()
{
using var mockHandler = new HttpMessageHandlerMock();
using var http = new HttpClient(mockHandler);
};
});
- var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
+ var request = new GetRequest
+ {
+ RequestUri = new("hoge/tetete.json", UriKind.Relative),
+ };
var exception = await Assert.ThrowsAsync<TwitterApiException>(
- () => apiConnection.GetAsync<string>(endpoint, null, endpointName: "/hoge/tetete")
+ () => apiConnection.SendAsync(request)
);
// エラーレスポンスの JSON に含まれるエラーコードに基づいてメッセージを出力する
Assert.Equal(0, mockHandler.QueueCount);
}
+
+ [Fact]
+ public async Task HandleTimeout_SuccessTest()
+ {
+ static async Task<int> AsyncFunc(CancellationToken token)
+ {
+ await Task.Delay(10);
+ token.ThrowIfCancellationRequested();
+ return 1;
+ }
+
+ var timeout = TimeSpan.FromMilliseconds(200);
+ var ret = await TwitterApiConnection.HandleTimeout(AsyncFunc, timeout);
+
+ Assert.Equal(1, ret);
+ }
+
+ [Fact]
+ public async Task HandleTimeout_TimeoutTest()
+ {
+ var tcs = new TaskCompletionSource<bool>();
+
+ async Task<int> AsyncFunc(CancellationToken token)
+ {
+ await Task.Delay(200);
+ tcs.SetResult(token.IsCancellationRequested);
+ return 1;
+ }
+
+ var timeout = TimeSpan.FromMilliseconds(10);
+ await Assert.ThrowsAsync<OperationCanceledException>(
+ () => TwitterApiConnection.HandleTimeout(AsyncFunc, timeout)
+ );
+
+ var cancelRequested = await tcs.Task;
+ Assert.True(cancelRequested);
+ }
+
+ [Fact]
+ public async Task HandleTimeout_ThrowExceptionAfterTimeoutTest()
+ {
+ var tcs = new TaskCompletionSource<int>();
+
+ async Task<int> AsyncFunc(CancellationToken token)
+ {
+ await Task.Delay(100);
+ tcs.SetResult(1);
+ throw new Exception();
+ }
+
+ var timeout = TimeSpan.FromMilliseconds(10);
+ await Assert.ThrowsAsync<OperationCanceledException>(
+ () => TwitterApiConnection.HandleTimeout(AsyncFunc, timeout)
+ );
+
+ // キャンセル後に AsyncFunc で発生した例外が無視される(UnobservedTaskException イベントを発生させない)ことをチェックする
+ var error = false;
+ void UnobservedExceptionHandler(object s, UnobservedTaskExceptionEventArgs e)
+ => error = true;
+
+ TaskScheduler.UnobservedTaskException += UnobservedExceptionHandler;
+
+ await tcs.Task;
+ await Task.Delay(10);
+ GC.Collect(); // UnobservedTaskException は Task のデストラクタで呼ばれるため強制的に GC を実行する
+ await Task.Delay(10);
+
+ Assert.False(error);
+
+ TaskScheduler.UnobservedTaskException -= UnobservedExceptionHandler;
+ }
}
}
[Theory]
[MemberData(nameof(CreateDataFromJsonTestCase))]
- public void CreateDataFromJsonTest<T>(string json, T expected)
+ public void CreateDataFromJson_StringTest<T>(string json, T expected)
=> Assert.Equal(expected, MyCommon.CreateDataFromJson<T>(json));
[Theory]
+ [MemberData(nameof(CreateDataFromJsonTestCase))]
+ public void CreateDataFromJson_BytesTest<T>(string json, T expected)
+ {
+ var jsonBytes = Encoding.UTF8.GetBytes(json);
+ Assert.Equal(expected, MyCommon.CreateDataFromJson<T>(jsonBytes));
+ }
+
+ [Theory]
[InlineData("hoge123@example.com", true)]
[InlineData("hogehoge", false)]
[InlineData("foo.bar@example.com", true)]
--- /dev/null
+// OpenTween - Client of Twitter
+// Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#nullable enable
+
+using System;
+using System.Net.Http;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using OpenTween.Api;
+
+namespace OpenTween.Connection
+{
+ public sealed class ApiResponse : IDisposable
+ {
+ public bool IsDisposed { get; private set; }
+
+ private readonly HttpResponseMessage responseMessage;
+
+ public ApiResponse(HttpResponseMessage responseMessage)
+ => this.responseMessage = responseMessage;
+
+ public void Dispose()
+ {
+ if (this.IsDisposed)
+ return;
+
+ this.responseMessage.Dispose();
+ this.IsDisposed = true;
+ }
+
+ public async Task<byte[]> ReadAsBytes()
+ {
+ using var content = this.responseMessage.Content;
+
+ return await content.ReadAsByteArrayAsync()
+ .ConfigureAwait(false);
+ }
+
+ public async Task<T> ReadAsJson<T>()
+ {
+ var responseBytes = await this.ReadAsBytes()
+ .ConfigureAwait(false);
+
+ try
+ {
+ return MyCommon.CreateDataFromJson<T>(responseBytes);
+ }
+ catch (SerializationException ex)
+ {
+ var responseText = Encoding.UTF8.GetString(responseBytes);
+ throw TwitterApiException.CreateFromException(ex, responseText);
+ }
+ }
+
+ public async Task<XElement> ReadAsJsonXml()
+ {
+ var responseBytes = await this.ReadAsBytes()
+ .ConfigureAwait(false);
+
+ using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(
+ responseBytes,
+ XmlDictionaryReaderQuotas.Max
+ );
+
+ try
+ {
+ return XElement.Load(jsonReader);
+ }
+ catch (XmlException ex)
+ {
+ var responseText = Encoding.UTF8.GetString(responseBytes);
+ throw new TwitterApiException("Invalid JSON", ex) { ResponseText = responseText };
+ }
+ }
+ }
+}
--- /dev/null
+// OpenTween - Client of Twitter
+// Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+
+namespace OpenTween.Connection
+{
+ public class GetRequest : IHttpRequest
+ {
+ public required Uri RequestUri { get; set; }
+
+ public IDictionary<string, string>? Query { get; set; }
+
+ public string? EndpointName { get; set; }
+
+ public HttpRequestMessage CreateMessage(Uri baseUri)
+ => new()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = BuildUriWithQuery(new(baseUri, this.RequestUri), this.Query),
+ };
+
+ public static Uri BuildUriWithQuery(Uri uri, IEnumerable<KeyValuePair<string, string>>? query)
+ {
+ if (query == null)
+ return uri;
+
+ if (!MyCommon.IsNullOrEmpty(uri.Query))
+ throw new NotSupportedException("Merging uri query is not supported");
+
+ return new Uri(uri, "?" + MyCommon.BuildQueryString(query));
+ }
+ }
+}
--- /dev/null
+// OpenTween - Client of Twitter
+// Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#nullable enable
+
+using System;
+using System.Threading.Tasks;
+
+namespace OpenTween.Connection
+{
+ public interface IApiConnection : IDisposable
+ {
+ Task<ApiResponse> SendAsync(IHttpRequest request);
+ }
+}
namespace OpenTween.Connection
{
- public interface IApiConnectionLegacy : IDisposable
+ public interface IApiConnectionLegacy : IApiConnection, IDisposable
{
Task<T> GetAsync<T>(Uri uri, IDictionary<string, string>? param, string? endpointName);
--- /dev/null
+// OpenTween - Client of Twitter
+// Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#nullable enable
+
+using System;
+using System.Net.Http;
+
+namespace OpenTween.Connection
+{
+ public interface IHttpRequest
+ {
+ string? EndpointName { get; }
+
+ HttpRequestMessage CreateMessage(Uri baseUri);
+ }
+}
namespace OpenTween.Connection
{
- public class TwitterApiConnection : IApiConnectionLegacy, IDisposable
+ public class TwitterApiConnection : IApiConnection, IApiConnectionLegacy, IDisposable
{
public static Uri RestApiBase { get; set; } = new("https://api.twitter.com/1.1/");
this.HttpStreaming.Timeout = Timeout.InfiniteTimeSpan;
}
- public async Task<T> GetAsync<T>(Uri uri, IDictionary<string, string>? param, string? endpointName)
+ public async Task<ApiResponse> SendAsync(IHttpRequest request)
{
+ var endpointName = request.EndpointName;
+
// レートリミット規制中はAPIリクエストを送信せずに直ちにエラーを発生させる
if (endpointName != null)
this.ThrowIfRateLimitExceeded(endpointName);
- var requestUri = new Uri(RestApiBase, uri);
-
- if (param != null)
- requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
-
- var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ using var requestMessage = request.CreateMessage(RestApiBase);
+ HttpResponseMessage? responseMessage = null;
try
{
- using var response = await this.Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
- .ConfigureAwait(false);
+ responseMessage = await HandleTimeout(
+ (token) => this.Http.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, token),
+ Networking.DefaultTimeout
+ );
if (endpointName != null)
- MyCommon.TwitterApiInfo.UpdateFromHeader(response.Headers, endpointName);
+ MyCommon.TwitterApiInfo.UpdateFromHeader(responseMessage.Headers, endpointName);
- await TwitterApiConnection.CheckStatusCode(response)
+ await TwitterApiConnection.CheckStatusCode(responseMessage)
.ConfigureAwait(false);
- using var content = response.Content;
- var responseText = await content.ReadAsStringAsync()
- .ConfigureAwait(false);
+ var response = new ApiResponse(responseMessage);
+ responseMessage = null; // responseMessage は ApiResponse で使用するため破棄されないようにする
- try
- {
- return MyCommon.CreateDataFromJson<T>(responseText);
- }
- catch (SerializationException ex)
- {
- throw TwitterApiException.CreateFromException(ex, responseText);
- }
+ return response;
}
catch (HttpRequestException ex)
{
{
throw TwitterApiException.CreateFromException(ex);
}
+ finally
+ {
+ responseMessage?.Dispose();
+ }
+ }
+
+ public async Task<T> GetAsync<T>(Uri uri, IDictionary<string, string>? param, string? endpointName)
+ {
+ var request = new GetRequest
+ {
+ RequestUri = uri,
+ Query = param,
+ EndpointName = endpointName,
+ };
+
+ using var response = await this.SendAsync(request)
+ .ConfigureAwait(false);
+
+ return await response.ReadAsJson<T>()
+ .ConfigureAwait(false);
}
/// <summary>
}
}
+ public static async Task<T> HandleTimeout<T>(Func<CancellationToken, Task<T>> func, TimeSpan timeout)
+ {
+ using var cts = new CancellationTokenSource();
+ var cancellactionToken = cts.Token;
+
+ var task = Task.Run(() => func(cancellactionToken), cancellactionToken);
+ var timeoutTask = Task.Delay(timeout);
+
+ if (await Task.WhenAny(task, timeoutTask) == timeoutTask)
+ {
+ // タイムアウト
+
+ // キャンセル後のタスクで発生した例外は無視する
+ static async Task IgnoreExceptions(Task task)
+ {
+ try
+ {
+ await task.ConfigureAwait(false);
+ }
+ catch
+ {
+ }
+ }
+ _ = IgnoreExceptions(task);
+ cts.Cancel();
+
+ throw new OperationCanceledException("Timeout", cancellactionToken);
+ }
+
+ return await task;
+ }
+
protected static async Task CheckStatusCode(HttpResponseMessage response)
{
var statusCode = response.StatusCode;
}
public static T CreateDataFromJson<T>(string content)
+ => MyCommon.CreateDataFromJson<T>(Encoding.UTF8.GetBytes(content));
+
+ public static T CreateDataFromJson<T>(byte[] bytes)
{
- var buf = Encoding.Unicode.GetBytes(content);
- using var stream = new MemoryStream(buf);
+ using var stream = new MemoryStream(bytes);
var settings = new DataContractJsonSerializerSettings
{
UseSimpleDictionaryFormat = true,