Projekt

Obecné

Profil

Stáhnout (8.57 KB) Statistiky
| Větev: | Tag: | Revize:
1 3c82c2ad Pultak
using System.Diagnostics;
2
using System.Net.Http.Json;
3 88eab964 Pultak
using System.Text;
4 3c82c2ad Pultak
using System.Text.Json;
5
using System.Text.Json.Serialization;
6 88eab964 Pultak
using DiskQueue;
7 3c82c2ad Pultak
using LDClient.network.data;
8
9
namespace LDClient.network {
10 f281acac silhavyj
    
11 678a6a2b silhavyj
    /// <summary>
12
    /// This class implements IApiClient which is an interface
13
    /// defining all the functionality required from an API client.
14
    /// </summary>
15 33c231a4 silhavyj
    public sealed class ApiClient : IApiClient {
16 3c82c2ad Pultak
        
17 678a6a2b silhavyj
        /// <summary>
18
        /// Instance of an HTTP client the is used to send data off to the server 
19
        /// </summary>
20 afafbc22 Pultak
        public IHttpClient _client;
21 fa084fc4 Pultak
22 678a6a2b silhavyj
        /// <summary>
23
        /// Flag used to stop the client (periodical retrieval from the cache)
24
        /// </summary>
25 fa084fc4 Pultak
        public bool ClientRunning;
26
27 678a6a2b silhavyj
        /// <summary>
28
        /// Number of milliseconds after which the class tries to resend failed payloads to the server.
29
        /// </summary>
30 3c82c2ad Pultak
        private readonly uint _retryPeriod;
31 678a6a2b silhavyj
        
32
        /// <summary>
33
        /// Maximum number of entries (payloads) that can be sent to the server within one period (_retryPeriod).
34
        /// </summary>
35 88eab964 Pultak
        private readonly uint _maxEntries;
36 678a6a2b silhavyj
        
37
        /// <summary>
38
        /// Maximum number of failed payloads to be kept in the file-based cache (FIFO - when the maximum number is reached)
39
        /// </summary>
40 88eab964 Pultak
        private readonly uint _maxRetries;
41 fa084fc4 Pultak
        private readonly IPersistentQueue _cache;
42
43 678a6a2b silhavyj
        /// <summary>
44
        /// Creates an instance of the class.
45
        /// </summary>
46
        /// <param name="url">IP address of the server (url in case a DNS server is being used)</param>
47
        /// <param name="port">port that the API is running on</param>
48
        /// <param name="path">path of the API e.g. /api/v1/lg-logs</param>
49
        /// <param name="retryPeriod">number of milliseconds after which the class tries to resend failed payloads to the server</param>
50
        /// <param name="maxEntries">maximum number of entries (payloads) that can be sent to the server within one period</param>
51
        /// <param name="maxRetries">maximum number of failed payloads to be kept in the file-based cache</param>
52
        /// <param name="cache">instance of a persistent cache for storing failed payloads</param>
53 fa084fc4 Pultak
        public ApiClient(string url, uint port, string path, uint retryPeriod, uint maxEntries, uint maxRetries, IPersistentQueue cache) {
54 678a6a2b silhavyj
            // Construct the entire path to the API.
55 afafbc22 Pultak
            var uri = $"{url}:{port}{path}";
56 678a6a2b silhavyj
            
57
            // Store the values into class variables.
58 3c82c2ad Pultak
            _retryPeriod = retryPeriod;
59 88eab964 Pultak
            _maxEntries = maxEntries;
60
            _maxRetries = maxRetries;
61 678a6a2b silhavyj
            _cache = cache;
62 88eab964 Pultak
63 678a6a2b silhavyj
            // Create an instance of a HttpClient which takes care of
64
            // establishing a connection to the server;
65 afafbc22 Pultak
            _client = new HttpClient(uri);
66 3c82c2ad Pultak
        }
67
68 678a6a2b silhavyj
        /// <summary>
69
        /// Sends a payload to the server (API).
70
        /// </summary>
71
        /// <param name="payload">instance of a payload to be sent off to the server</param>
72 3c82c2ad Pultak
        public async Task SendPayloadAsync(Payload payload) {
73 a7d16717 Pultak
            Program.DefaultLogger.Debug("SendPayloadAsync called.");
74 3c82c2ad Pultak
            try {
75 678a6a2b silhavyj
                // Create an instance of Stopwatch (to measure how much
76
                // the action took).
77 3c82c2ad Pultak
                Stopwatch stopWatch = new();
78 522ba9b4 Pultak
                
79 678a6a2b silhavyj
                // Send the payload to the server.
80
                stopWatch.Start();
81 afafbc22 Pultak
                var response = await _client.PostAsJsonAsync(payload);
82 3c82c2ad Pultak
                stopWatch.Stop();
83 678a6a2b silhavyj
                
84
                // Create a log message.
85 522ba9b4 Pultak
                CreateRequestLog(payload, response, stopWatch.ElapsedMilliseconds);
86 678a6a2b silhavyj
                
87
                // Make sure the request was successful.
88 3c82c2ad Pultak
                response.EnsureSuccessStatusCode();
89
            } catch (Exception e) {
90
                Program.DefaultLogger.Error($"Failed to send {payload} to the server. Due to: {e.Message}");
91 522ba9b4 Pultak
                CachePayload(payload);
92 3c82c2ad Pultak
            }
93
        }
94
95 678a6a2b silhavyj
        /// <summary>
96
        /// Creates a request log message.
97
        /// </summary>
98
        /// <param name="payload">payload involved in the process of sending data to the server</param>
99
        /// <param name="response">response from the server</param>
100
        /// <param name="durationMs">duration in milliseconds (how much time it took to send off the payload)</param>
101 522ba9b4 Pultak
        private static void CreateRequestLog(Payload payload, HttpResponseMessage response, long durationMs) {
102 678a6a2b silhavyj
            // Create the log message.
103 0ba79cc3 Pultak
            var responseToLog = new {
104
                statusCode = response.StatusCode,
105
                content = response.Content,
106
                headers = response.Headers,
107
                errorMessage = response.RequestMessage,
108
            };
109 678a6a2b silhavyj
            
110
            // Log the message using the logger defined in Program (main class).
111 0ba79cc3 Pultak
            Program.DefaultLogger.Info($"Request completed in {durationMs} ms,\n" +
112 678a6a2b silhavyj
                                       $"Request body: {payload},\n" +
113
                                       $"Response: {responseToLog}");
114 522ba9b4 Pultak
        }
115
        
116 678a6a2b silhavyj
        /// <summary>
117
        /// Resends unsuccessful payloads to the server.
118
        /// </summary>
119 eda1e8c7 Pultak
        private async Task ResendPayloadsAsync() {
120 678a6a2b silhavyj
            // Calculate the maximum number of payloads to be sent to the server.
121 eda1e8c7 Pultak
            var numberOfPayloadsToResend = Math.Min(_maxRetries, _cache.EstimatedCountOfItemsInQueue);
122 678a6a2b silhavyj
            
123
            // Create a list for those payloads
124 eda1e8c7 Pultak
            var payloads = new List<Payload>();
125 678a6a2b silhavyj
            
126
            // Retrieve the payloads from the cache.
127 fa084fc4 Pultak
            if (numberOfPayloadsToResend > 0) {
128 678a6a2b silhavyj
                // Open up a session to the cache.
129 fa084fc4 Pultak
                using var session = _cache.OpenSession();
130 678a6a2b silhavyj
                
131
                // Pop out payloads, deserialize them, and store them into the list.
132 eda1e8c7 Pultak
                for (var i = 0; i < numberOfPayloadsToResend; i++) {
133
                    var rawBytes = session.Dequeue();
134
                    var payload = JsonSerializer.Deserialize<Payload>(rawBytes);
135
                    if (payload is not null) {
136
                        payloads.Add(payload);
137
                    }
138
                }
139 678a6a2b silhavyj
                // Flush the changes.
140 eda1e8c7 Pultak
                session.Flush();
141
            }
142 678a6a2b silhavyj
            
143
            // If there are some payloads to be resent to the server.
144 eda1e8c7 Pultak
            if (payloads.Count > 0) {
145
                Program.DefaultLogger.Debug($"ResendPayloadAsync -> {payloads.Count} unsent payloads");
146
                var tasks = new List<Task>();
147 678a6a2b silhavyj
                
148
                // Create a separate task for each payload - resend them to the server.
149 eda1e8c7 Pultak
                foreach (var payload in payloads) {
150
                    Program.DefaultLogger.Info($"Resending {payload}.");
151
                    tasks.Add(SendPayloadAsync(payload));
152
                }
153 678a6a2b silhavyj
                // Wait until all tasks are finished. 
154 eda1e8c7 Pultak
                await Task.WhenAll(tasks);
155
            }
156
        }
157 f281acac silhavyj
        
158 678a6a2b silhavyj
        /// <summary>
159
        /// Stores a failed payload into a persistent cache.
160
        /// </summary>
161
        /// <param name="payload"></param>
162 eda1e8c7 Pultak
        private void CachePayload(Payload payload) {
163
            Program.DefaultLogger.Info($"Storing {payload} into the cache.");
164 678a6a2b silhavyj
            
165
            // Number of payloads stored in the cache.
166 eda1e8c7 Pultak
            var numberOfCachedPayloads = _cache.EstimatedCountOfItemsInQueue;
167 678a6a2b silhavyj
            
168
            // Open up a session to the cache.
169 eda1e8c7 Pultak
            using var session = _cache.OpenSession();
170 678a6a2b silhavyj
            
171
            // If the cache is "full", make room for the latest failed
172
            // payload by discarding the oldest one.
173 eda1e8c7 Pultak
            if (numberOfCachedPayloads >= _maxEntries) {
174
                session.Dequeue();
175
            }
176 678a6a2b silhavyj
            
177
            // Store the payload into the cache.
178 eda1e8c7 Pultak
            var payloadJson = JsonSerializer.Serialize(payload);
179
            session.Enqueue(Encoding.UTF8.GetBytes(payloadJson));
180 678a6a2b silhavyj
            
181
            // Flush the changes.
182 eda1e8c7 Pultak
            session.Flush();
183
        }
184
185 678a6a2b silhavyj
        /// <summary>
186
        /// Runs the periodical retrieval of failed payloads stored
187
        /// in a file-based cache. This method is instantiated as a thread.
188
        /// </summary>
189 88eab964 Pultak
        public async void Run() {
190
            Program.DefaultLogger.Info("Api Client thread has started");
191 678a6a2b silhavyj
            
192
            // Keep the thread running.
193 fa084fc4 Pultak
            ClientRunning = true;
194 678a6a2b silhavyj
            
195
            // Keep resending failed payloads to the server.
196 fa084fc4 Pultak
            while (ClientRunning) {
197 88eab964 Pultak
                await ResendPayloadsAsync();
198 f281acac silhavyj
                Thread.Sleep((int) _retryPeriod);
199 88eab964 Pultak
            }
200 0ba79cc3 Pultak
        }
201 3c82c2ad Pultak
    }
202
}