-
Notifications
You must be signed in to change notification settings - Fork 4
/
HttpMessageHandler.cpp
209 lines (177 loc) · 6.99 KB
/
HttpMessageHandler.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// Dependencies
#include "HttpMessageHandler.h"
#include "MongooseUtils.h"
#include "SharedMemoryRenderer.h"
#include "Utils.h"
#include "Globals.h"
#include "mongoose.h"
#include <sstream>
#include "time.h"
// Constants
#define MAP_OBJECT_NAME "$pcars2$"
#define HTTP_RESPONSE_503 "{\r\n \"status\": \"503 Service unavailable, is PCARS2 or AMS2 running and is Shared Memory enabled?\"\r\n}"
#define HTTP_RESPONSE_409 "{\r\n \"status\": \"409 Conflict, are CREST and PCARS2 or AMS2 both at the latest version?\"\r\n}"
#define GZIP_THRESHOLD 128
static SharedMemoryRenderer sharedMemoryRenderer = SharedMemoryRenderer();
HttpMessageHandler::HttpMessageHandler(){};
// Outputs an HTTP 503 on the supplied connection
void sendServiceUnavailable(struct mg_connection *nc) {
// Send HTTP 503
mg_printf(nc, "HTTP/1.1 503 Service unavailable\r\n"
"Content-Type: application/json\r\n"
"Cache-Control: no-cache\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Content-Length: %d\r\n\r\n%s",
(int)strlen(HTTP_RESPONSE_503), HTTP_RESPONSE_503);
}
// Outputs an HTTP 409 on the supplied connection
void sendConflict(struct mg_connection *nc) {
// Send HTTP 409
mg_printf(nc, "HTTP/1.1 409 Conflict\r\n"
"Content-Type: application/json\r\n"
"Cache-Control: no-cache\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Content-Length: %d\r\n\r\n%s",
(int)strlen(HTTP_RESPONSE_409), HTTP_RESPONSE_409);
}
// Outputs an HTTP 200 on the supplied connection for an OPTIONS request
void sendOptions(struct mg_connection *nc) {
// Send HTTP 200
mg_printf(nc, "HTTP/1.1 200 Ok\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Access-Control-Allow-Methods: GET, OPTIONS\r\n"
"Access-Control-Max-Age: 86400\r\n"
"Content-Length: 0\r\n");
}
// Extracts the query string from the given HTTP message
std::string getQueryString(struct http_message *hm) {
if (hm->query_string.len > 0) {
std::string queryString (hm->query_string.p, hm->query_string.len);
return queryString;
}else{
return "";
}
}
// Extracts the request method from the given HTTP message
std::string getMethod(struct http_message *hm) {
if (hm->method.len > 0) {
std::string requestMethod (hm->method.p, hm->method.len);
return requestMethod;
}else{
return "";
}
}
// Returns true if the response to the given HTTP message should
// be gzipped, based on the value of the Accept-Encoding header
// and the size of the uncompressed response
bool shouldGzipResponse(struct http_message *hm, int responseLength) {
return Utils::contains(FossaUtils::getHeaderValue("Accept-Encoding", hm), "gzip") && responseLength > GZIP_THRESHOLD;
}
// Renders the response
void renderResponse(struct mg_connection *nc, const SharedMemory* sharedData, struct http_message *hm) {
// get current time for debugging info
char sTime[100];
time_t TimeNow = time(0);
strftime(sTime, 100, "%H:%M:%S", localtime(&TimeNow));
// Odd sequence number indicates, that write into the shared memory is just happening, go on with even sequence number only
if (sharedData->mSequenceNumber % 2)
{
// activate it for a kind of debugging mode
if (debug_level > 0) {
printf("%s: INFO - Odd sequence number detected - Data not accessable during write process by game\n",sTime);
}
}
else {
indexChange = sharedData->mSequenceNumber - updateIndex;
updateIndex = sharedData->mSequenceNumber;
//Copy the whole structure before processing it, otherwise the risk of the game writing into it during processing is too high.
memcpy(localCopyTmp, sharedData, sizeof(SharedMemory));
if (localCopyTmp->mSequenceNumber != updateIndex)
{
// More writes had happened during the read. Should be rare, but can happen.
// activate it for a kind of debugging mode
if (debug_level > 0) {
printf("%s: INFO - Sequence number mismatch detected - Data not accessable during write process by game\n", sTime);
}
}
else {
// At this point all checks are passed without problem and the localCopy can be updated with new data.
// In all other error cases the data from the previous loop pass is used
memcpy(localCopy, localCopyTmp, sizeof(SharedMemory));
}
}
//for debugging
if (debug_level > 1) {
printf("%s: INFO - Sequence number increase %d, current index %d, previous index %d\n", sTime, indexChange, localCopy->mSequenceNumber, updateIndex);
}
// old way with direct access to the Shared Memory data
//std::string responseJson = sharedMemoryRenderer.render(sharedData, getQueryString(hm));
// new way with using the local copy data
std::string responseJson = sharedMemoryRenderer.render(localCopy, getQueryString(hm));
std::string response;
bool gzipResponse = shouldGzipResponse(hm, responseJson.size());
if (gzipResponse) {
response = Utils::gzipString(responseJson);
}
else {
response = responseJson;
}
// build HTTP OK response with JSON response body
mg_printf(nc, "HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Cache-Control: no-cache\r\n"
"Access-Control-Allow-Origin: *\r\n");
if (gzipResponse) {
mg_printf(nc, "Content-Encoding: gzip\r\n");
}
mg_printf(nc, "Content-Length: %d\r\n\r\n",
(int)response.size());
mg_send(nc, response.data(), response.size());
}
// Processes the shared memory
void processSharedMemoryData(struct mg_connection *nc, const SharedMemory* sharedData, struct http_message *hm) {
// Ensure we're sync'd to the correct data version
if (sharedData->mVersion != SHARED_MEMORY_VERSION) {
// build conflict response
sendConflict(nc);
printf("Data version mismatch, please make sure that your pCARS version matches your CREST version\n Expected: v%d, Current: v%d\n", SHARED_MEMORY_VERSION, sharedData->mVersion);
}else{
renderResponse(nc, sharedData, hm);
}
}
// Processes the memory mapped file
void processFile(struct mg_connection *nc, HANDLE fileHandle, struct http_message *hm) {
const SharedMemory* sharedData = (SharedMemory*)MapViewOfFile(fileHandle, PAGE_READONLY, 0, 0, sizeof(SharedMemory));
if (sharedData == NULL) {
// File found, but could not be mapped to shared memory data
sendServiceUnavailable(nc);
}
else{
// Process file
processSharedMemoryData(nc, sharedData, hm);
// Unmap file
UnmapViewOfFile(sharedData);
}
}
void handleGet(struct mg_connection *nc, struct http_message *hm) {
// Open the memory mapped file
HANDLE fileHandle = OpenFileMappingA(PAGE_READONLY, FALSE, MAP_OBJECT_NAME);
if (fileHandle == NULL) {
// File is not available, build service unavailable response
sendServiceUnavailable(nc);
}
else{
// File is available, process the file
processFile(nc, fileHandle, hm);
// Close the file
CloseHandle(fileHandle);
}
}
void HttpMessageHandler::handle(struct mg_connection *nc, struct http_message *hm) {
std::string requestMethod = getMethod(hm);
if (requestMethod.compare("GET") == 0) {
handleGet(nc, hm);
}else if (requestMethod.compare("OPTIONS") == 0) {
sendOptions(nc);
}
}