OSDN Git Service

PostMultipartRequestクラスを追加
authorKimura Youichi <kim.upsilon@bucyou.net>
Mon, 11 Dec 2023 18:35:52 +0000 (03:35 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Tue, 12 Dec 2023 17:51:19 +0000 (02:51 +0900)
OpenTween.Tests/Connection/PostMultipartRequestTest.cs [new file with mode: 0644]
OpenTween.Tests/Connection/TwitterApiConnectionTest.cs
OpenTween/Connection/PostMultipartRequest.cs [new file with mode: 0644]
OpenTween/Connection/TwitterApiConnection.cs

diff --git a/OpenTween.Tests/Connection/PostMultipartRequestTest.cs b/OpenTween.Tests/Connection/PostMultipartRequestTest.cs
new file mode 100644 (file)
index 0000000..043770d
--- /dev/null
@@ -0,0 +1,90 @@
+// 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.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace OpenTween.Connection
+{
+    public class PostMultipartRequestTest
+    {
+        [Fact]
+        public async Task CreateMessage_Test()
+        {
+            using var image = TestUtils.CreateDummyImage();
+            using var media = new MemoryImageMediaItem(image);
+
+            var request = new PostMultipartRequest
+            {
+                RequestUri = new("hoge/aaa.json", UriKind.Relative),
+                Query = new Dictionary<string, string>
+                {
+                    ["aaaa"] = "1111",
+                    ["bbbb"] = "2222",
+                },
+                Media = new Dictionary<string, IMediaItem>
+                {
+                    ["media1"] = media,
+                },
+            };
+
+            var baseUri = new Uri("https://example.com/v1/");
+            using var requestMessage = request.CreateMessage(baseUri);
+
+            Assert.Equal(HttpMethod.Post, requestMessage.Method);
+            Assert.Equal(new("https://example.com/v1/hoge/aaa.json"), requestMessage.RequestUri);
+
+            using var requestContent = Assert.IsType<MultipartFormDataContent>(requestMessage.Content);
+            var boundary = requestContent.Headers.ContentType.Parameters.Cast<NameValueHeaderValue>()
+                .First(y => y.Name == "boundary").Value;
+
+            // 前後のダブルクオーテーションを除去
+            boundary = boundary.Substring(1, boundary.Length - 2);
+
+            var expectedText =
+                $"--{boundary}\r\n" +
+                "Content-Type: text/plain; charset=utf-8\r\n" +
+                "Content-Disposition: form-data; name=aaaa\r\n" +
+                "\r\n" +
+                "1111\r\n" +
+                $"--{boundary}\r\n" +
+                "Content-Type: text/plain; charset=utf-8\r\n" +
+                "Content-Disposition: form-data; name=bbbb\r\n" +
+                "\r\n" +
+                "2222\r\n" +
+                $"--{boundary}\r\n" +
+                $"Content-Disposition: form-data; name=media1; filename={media.Name}; filename*=utf-8''{media.Name}\r\n" +
+                "\r\n";
+
+            var expected = Encoding.UTF8.GetBytes(expectedText)
+                .Concat(image.Stream.ToArray())
+                .Concat(Encoding.UTF8.GetBytes($"\r\n--{boundary}--\r\n"));
+
+            Assert.Equal(expected, await requestContent.ReadAsByteArrayAsync());
+        }
+    }
+}
index cc89356..37082ca 100644 (file)
@@ -326,7 +326,7 @@ namespace OpenTween.Connection
             using var mockHandler = new HttpMessageHandlerMock();
             using var http = new HttpClient(mockHandler);
             using var apiConnection = new TwitterApiConnection();
-            apiConnection.HttpUpload = http;
+            apiConnection.Http = http;
 
             using var image = TestUtils.CreateDummyImage();
             using var media = new MemoryImageMediaItem(image);
@@ -396,7 +396,7 @@ namespace OpenTween.Connection
             using var mockHandler = new HttpMessageHandlerMock();
             using var http = new HttpClient(mockHandler);
             using var apiConnection = new TwitterApiConnection();
-            apiConnection.HttpUpload = http;
+            apiConnection.Http = http;
 
             mockHandler.Enqueue(async x =>
             {
diff --git a/OpenTween/Connection/PostMultipartRequest.cs b/OpenTween/Connection/PostMultipartRequest.cs
new file mode 100644 (file)
index 0000000..6e27e36
--- /dev/null
@@ -0,0 +1,66 @@
+// 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 PostMultipartRequest : IHttpRequest
+    {
+        public required Uri RequestUri { get; set; }
+
+        public IDictionary<string, string>? Query { get; set; }
+
+        public IDictionary<string, IMediaItem>? Media { get; set; }
+
+        public string? EndpointName { get; set; }
+
+        public TimeSpan Timeout { get; set; } = Networking.UploadImageTimeout;
+
+        public HttpRequestMessage CreateMessage(Uri baseUri)
+        {
+            var content = new MultipartFormDataContent();
+
+            if (this.Query != null)
+            {
+                foreach (var (key, value) in this.Query)
+                    content.Add(new StringContent(value), key);
+            }
+
+            if (this.Media != null)
+            {
+                foreach (var (key, value) in this.Media)
+                    content.Add(new StreamContent(value.OpenRead()), key, value.Name);
+            }
+
+            return new()
+            {
+                Method = HttpMethod.Post,
+                RequestUri = new(baseUri, this.RequestUri),
+                Content = content,
+            };
+        }
+    }
+}
index d17da21..3bd735d 100644 (file)
@@ -53,7 +53,6 @@ namespace OpenTween.Connection
         public bool IsDisposed { get; private set; } = false;
 
         internal HttpClient Http;
-        internal HttpClient HttpUpload;
 
         internal ITwitterCredential Credential { get; }
 
@@ -70,16 +69,13 @@ namespace OpenTween.Connection
             Networking.WebProxyChanged += this.Networking_WebProxyChanged;
         }
 
-        [MemberNotNull(nameof(Http), nameof(HttpUpload))]
+        [MemberNotNull(nameof(Http))]
         private void InitializeHttpClients()
         {
             this.Http = InitializeHttpClient(this.Credential);
 
             // タイムアウト設定は IHttpRequest.Timeout でリクエスト毎に制御する
             this.Http.Timeout = Timeout.InfiniteTimeSpan;
-
-            this.HttpUpload = InitializeHttpClient(this.Credential);
-            this.HttpUpload.Timeout = Networking.UploadImageTimeout;
         }
 
         public async Task<ApiResponse> SendAsync(IHttpRequest request)
@@ -179,86 +175,31 @@ namespace OpenTween.Connection
 
         public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string>? param, IDictionary<string, IMediaItem>? media)
         {
-            var requestUri = new Uri(RestApiBase, uri);
-            var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
-
-            using var postContent = new MultipartFormDataContent();
-            if (param != null)
-            {
-                foreach (var (key, value) in param)
-                    postContent.Add(new StringContent(value), key);
-            }
-            if (media != null)
-            {
-                foreach (var (key, value) in media)
-                    postContent.Add(new StreamContent(value.OpenRead()), key, value.Name);
-            }
-
-            request.Content = postContent;
-
-            HttpResponseMessage? response = null;
-            try
+            var request = new PostMultipartRequest
             {
-                response = await this.HttpUpload.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
-                    .ConfigureAwait(false);
-
-                await TwitterApiConnection.CheckStatusCode(response)
-                    .ConfigureAwait(false);
+                RequestUri = uri,
+                Query = param,
+                Media = media,
+            };
 
-                var result = new LazyJson<T>(response);
-                response = null;
+            using var response = await this.SendAsync(request)
+                .ConfigureAwait(false);
 
-                return result;
-            }
-            catch (HttpRequestException ex)
-            {
-                throw TwitterApiException.CreateFromException(ex);
-            }
-            catch (OperationCanceledException ex)
-            {
-                throw TwitterApiException.CreateFromException(ex);
-            }
-            finally
-            {
-                response?.Dispose();
-            }
+            return response.ReadAsLazyJson<T>();
         }
 
         public async Task PostAsync(Uri uri, IDictionary<string, string>? param, IDictionary<string, IMediaItem>? media)
         {
-            var requestUri = new Uri(RestApiBase, uri);
-            var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
-
-            using var postContent = new MultipartFormDataContent();
-            if (param != null)
-            {
-                foreach (var (key, value) in param)
-                    postContent.Add(new StringContent(value), key);
-            }
-            if (media != null)
-            {
-                foreach (var (key, value) in media)
-                    postContent.Add(new StreamContent(value.OpenRead()), key, value.Name);
-            }
-
-            request.Content = postContent;
-
-            try
+            var request = new PostMultipartRequest
             {
-                using var response = await this.HttpUpload.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
-                    .ConfigureAwait(false);
+                RequestUri = uri,
+                Query = param,
+                Media = media,
+            };
 
-                await TwitterApiConnection.CheckStatusCode(response)
-                    .ConfigureAwait(false);
-            }
-            catch (HttpRequestException ex)
-            {
-                throw TwitterApiException.CreateFromException(ex);
-            }
-            catch (OperationCanceledException ex)
-            {
-                throw TwitterApiException.CreateFromException(ex);
-            }
+            await this.SendAsync(request)
+                .IgnoreResponse()
+                .ConfigureAwait(false);
         }
 
         public static async Task<T> HandleTimeout<T>(Func<CancellationToken, Task<T>> func, TimeSpan timeout)
@@ -385,7 +326,6 @@ namespace OpenTween.Connection
             {
                 Networking.WebProxyChanged -= this.Networking_WebProxyChanged;
                 this.Http.Dispose();
-                this.HttpUpload.Dispose();
             }
         }