• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-dotnet / 5716220196

31 Jul 2023 02:52PM UTC coverage: 82.478% (-0.3%) from 82.741%
5716220196

Pull #3261

github

aed2eb
fealebenpae
Merge remote-tracking branch 'origin/main' into yg/updated-marshaling

# Conflicts:
#	Realm/Realm/Handles/SharedRealmHandle.cs
Pull Request #3261: Use modern-er marshaling techniques

2029 of 2601 branches covered (78.01%)

Branch coverage included in aggregate %.

201 of 201 new or added lines in 21 files covered. (100.0%)

6246 of 7432 relevant lines covered (84.04%)

34048.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

50.0
/Realm/Realm/Native/HttpClientTransport.cs
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2020 Realm Inc.
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
//
17
////////////////////////////////////////////////////////////////////////////
18

19
using System;
20
using System.Collections.Generic;
21
using System.Diagnostics;
22
using System.Linq;
23
using System.Net.Http;
24
using System.Runtime.InteropServices;
25
using System.Text;
26
using System.Threading;
27
using System.Threading.Tasks;
28
using Realms.Sync.Native;
29

30
namespace Realms.Native
31
{
32
    internal static class HttpClientTransport
33
    {
34
        private enum CustomErrorCode
35
        {
36
            NoError = 0,
37
            HttpClientDisposed = 997,
38
            UnknownHttp = 998,
39
            Unknown = 999,
40
            Timeout = 1000,
41
        }
42

43
#pragma warning disable SA1300 // Element should begin with upper-case letter
44

45
        private enum NativeHttpMethod
46
        {
47
            get,
48
            post,
49
            patch,
50
            put,
51
            del
52
        }
53

54
        [StructLayout(LayoutKind.Sequential)]
55
        private struct HttpClientRequest
56
        {
57
            public NativeHttpMethod method;
58

59
            private StringValue url;
60

61
            public UInt64 timeout_ms;
62

63
            public MarshaledVector<KeyValuePair<StringValue, StringValue>> headers;
64

65
            private StringValue body;
66

67
            private IntPtr managed_http_client;
68

69
            public string Url => url!;
2,925✔
70

71
            public string? Body => body;
1,927✔
72

73
            public HttpClient HttpClient
74
            {
75
                get
76
                {
77
                    if (managed_http_client != IntPtr.Zero &&
2,925!
78
                        GCHandle.FromIntPtr(managed_http_client).Target is HttpClient client)
2,925✔
79
                    {
80
                        return client;
2,925✔
81
                    }
82

83
                    throw new ObjectDisposedException("HttpClient has been disposed, most likely because the app has been closed.");
×
84
                }
85
            }
86
        }
87

88
        [StructLayout(LayoutKind.Sequential)]
89
        private struct HttpClientResponse
90
        {
91
            public Int32 http_status_code;
92

93
            public CustomErrorCode custom_status_code;
94

95
            public MarshaledVector<KeyValuePair<StringValue, StringValue>> headers;
96

97
            public StringValue body;
98
        }
99

100
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
101
        private delegate void execute_request(HttpClientRequest request, IntPtr callback_ptr);
102

103
        [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_http_transport_install_callbacks", CallingConvention = CallingConvention.Cdecl)]
104
        private static extern void install_callbacks(execute_request execute);
105

106
        [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_http_transport_respond", CallingConvention = CallingConvention.Cdecl)]
107
        private static extern void respond(HttpClientResponse response, IntPtr callback_ptr);
108

109
#pragma warning restore SA1300 // Element should begin with upper-case letter
110

111
        internal static void Initialize()
112
        {
113
            execute_request execute = ExecuteRequest;
1✔
114

115
            GCHandle.Alloc(execute);
1✔
116

117
            install_callbacks(execute);
1✔
118
        }
1✔
119

120
        private static HttpRequestMessage BuildRequest(HttpClientRequest request)
121
        {
122
            var message = new HttpRequestMessage(request.method.ToHttpMethod(), request.Url);
2,925✔
123
            foreach (var header in request.headers)
18,800✔
124
            {
125
                message.Headers.TryAddWithoutValidation(header.Key!, header.Value);
6,475✔
126
            }
127

128
            if (request.method != NativeHttpMethod.get)
2,925✔
129
            {
130
                message.Content = new StringContent(request.Body!, Encoding.UTF8, "application/json");
1,927✔
131
            }
132

133
            return message;
2,925✔
134
        }
135

136
        [MonoPInvokeCallback(typeof(execute_request))]
137
        private static async void ExecuteRequest(HttpClientRequest request, IntPtr callback)
138
        {
139
            try
140
            {
141
                using var arena = new Arena();
2,925✔
142
                try
143
                {
144
                    var httpClient = request.HttpClient;
2,925✔
145

146
                    using var message = BuildRequest(request);
2,925✔
147
                    using var cts = new CancellationTokenSource((int)request.timeout_ms);
2,925✔
148

149
                    var response = await httpClient.SendAsync(message, cts.Token).ConfigureAwait(false);
2,925✔
150

151
                    var headers = response.Headers.Concat(response.Content.Headers)
2,925✔
152
                        .Select(h => new KeyValuePair<StringValue, StringValue>(StringValue.AllocateFrom(h.Key, arena), StringValue.AllocateFrom(h.Value.FirstOrDefault(), arena)))
25,638✔
153
                        .ToArray();
2,925✔
154

155
                    var nativeResponse = new HttpClientResponse
2,925✔
156
                    {
2,925✔
157
                        http_status_code = (int)response.StatusCode,
2,925✔
158
                        headers = MarshaledVector<KeyValuePair<StringValue, StringValue>>.AllocateFrom(headers, arena),
2,925✔
159
                        body = StringValue.AllocateFrom(await response.Content.ReadAsStringAsync().ConfigureAwait(false), arena),
2,925✔
160
                    };
2,925✔
161

162
                    respond(nativeResponse, callback);
2,925✔
163
                }
2,925✔
164
                catch (HttpRequestException rex)
×
165
                {
166
                    var sb = new StringBuilder("An unexpected error occurred while sending the request");
×
167

168
                    // We're doing this because the message for the top-level exception is usually pretty useless.
169
                    // If there's inner exception, we want to skip it and directly go for the more specific messages.
170
                    var innerEx = rex.InnerException ?? rex;
×
171
                    while (innerEx != null)
×
172
                    {
173
                        sb.Append($": {innerEx.Message}");
×
174
                        innerEx = innerEx.InnerException;
×
175
                    }
176

177
                    var nativeResponse = new HttpClientResponse
×
178
                    {
×
179
                        custom_status_code = CustomErrorCode.UnknownHttp,
×
180
                        body = StringValue.AllocateFrom(sb.ToString(), arena),
×
181
                    };
×
182

183
                    respond(nativeResponse, callback);
×
184
                }
×
185
                catch (TaskCanceledException)
×
186
                {
187
                    var nativeResponse = new HttpClientResponse
×
188
                    {
×
189
                        custom_status_code = CustomErrorCode.Timeout,
×
190
                        body = StringValue.AllocateFrom($"Operation failed to complete within {request.timeout_ms} ms.", arena),
×
191
                    };
×
192

193
                    respond(nativeResponse, callback);
×
194
                }
×
195
                catch (ObjectDisposedException ode)
×
196
                {
197
                    var nativeResponse = new HttpClientResponse
×
198
                    {
×
199
                        custom_status_code = CustomErrorCode.HttpClientDisposed,
×
200
                        body = StringValue.AllocateFrom(ode.Message, arena),
×
201
                    };
×
202

203
                    respond(nativeResponse, callback);
×
204
                }
×
205
                catch (Exception ex)
×
206
                {
207
                    var nativeResponse = new HttpClientResponse
×
208
                    {
×
209
                        custom_status_code = CustomErrorCode.Unknown,
×
210
                        body = StringValue.AllocateFrom(ex.Message, arena),
×
211
                    };
×
212

213
                    respond(nativeResponse, callback);
×
214
                }
×
215
            }
2,925✔
216
            catch (Exception outerEx)
×
217
            {
218
                Debug.WriteLine($"Unexpected error occurred while trying to respond to a request: {outerEx}");
219
            }
×
220
        }
2,925✔
221

222
        private static HttpMethod ToHttpMethod(this NativeHttpMethod nativeMethod)
223
        {
224
            return nativeMethod switch
2,925!
225
            {
2,925✔
226
                NativeHttpMethod.get => HttpMethod.Get,
998✔
227
                NativeHttpMethod.post => HttpMethod.Post,
1,910✔
228
                NativeHttpMethod.patch => new HttpMethod("PATCH"),
×
229
                NativeHttpMethod.put => HttpMethod.Put,
11✔
230
                NativeHttpMethod.del => HttpMethod.Delete,
6✔
231
                _ => throw new NotSupportedException($"Unsupported HTTP method: {nativeMethod}")
×
232
            };
2,925✔
233
        }
234
    }
235
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc