-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.c
More file actions
457 lines (420 loc) · 14.5 KB
/
server.c
File metadata and controls
457 lines (420 loc) · 14.5 KB
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include "lib_server.h"
/**
* @brief Reading client's buffer at given position.
*
* Reading of client buffer into buf at position data_len (data_len is the
* size of already written data) for as many characters as possible (to fill
* the buffer buf or to read everything).
*
* Performs realloc when receiving buffer is full (double size).
*
* @param clientfd Client's file descriptor.
* @param p_buf Pointer to beginning of the receiving buffer.
* @param p_data_len Pointer to the size of already written data.
* @param p_buf_size Pointer to the maximum size of the receiving buffer.
*
* @return Integer:
* 0: client disconnected
* -1: fail
* n: read response, number of characters read
*/
ssize_t read_client(
int clientfd, char** p_buf, size_t* p_data_len, size_t* p_buf_size) {
ssize_t n = read(
clientfd, *p_buf + *p_data_len, *p_buf_size - *p_data_len);
if (n == 0) {
printf("Client %d disconnected\n", clientfd);
printf("-------------------------------------\n");
printf("-------------------------------------\n");
return 0;
}
if (n < 0) {
perror("read() failed");
return -1;
}
*p_data_len += (size_t) n; // negative cases already dealt with
if (*p_data_len == *p_buf_size) {
// Buf is full, need to realloc
*p_buf_size *= 2;
*p_buf = realloc(*p_buf, *p_buf_size);
// printf("REALLOC!! New size: %zu\n", buf_size);
}
return n;
}
/**
* @brief Generate HTTP response's content for given coordinates.
*
* @param x_coord Robot's x coordinate.
* @param y_coord Robot's y coordinate.
* @param x_max Value max for x (so number of lines in the grid - 1).
* @param y_max Value max for y (so number of columns in the grid - 1).
* @param content Pointer to string structure containing response content.
* @param robot_grid Pointer to string structure containing the robot grid.
* @param cookie Pointer to string structure containing cookies.
* @param p_tem p_tem Pointer to the structure containing all template data.
*
* @return Content-Type.
*/
const char* generate_content(int x_coord, int y_coord, int x_max, int y_max,
struct string* content, struct string* robot_grid, struct string* cookie,
struct templates* p_tem) {
int d = string_snprintf(
cookie,
"Set-Cookie: x=%d; Path=/\r\nSet-Cookie: y=%d; Path=/\r\n",
x_coord, y_coord
);
if (d < 0) {
return "text/html";
}
// Generate robot grid (HTML table)
string_clear(robot_grid);
generate_html_table(robot_grid, x_max, y_max, x_coord, y_coord);
// Create HTML response
d = string_snprintf(
content, p_tem->html_template, p_tem->favicon_data,
x_coord, y_coord, robot_grid->start
);
if (d < 0) {
return "text/html";
}
return "text/html";
}
/**
* @brief Handles a given client.
*
* @param clientfd Client's file descriptor.
* @param client_addr Client's address.
* @param p_tem Pointer to the structure containing all template data.
*/
void handle_client(
int clientfd, struct sockaddr_in client_addr, struct templates* p_tem) {
// Get client IP address in '0.0.0.0' format for printing
char client_ip_address[16];
const char* ret2 = inet_ntop(
AF_INET, &client_addr.sin_addr, client_ip_address,
sizeof client_ip_address);
if (ret2 == NULL) {
perror("inet_ntop() failed");
return;
}
printf("Client IP address: %s\n", client_ip_address);
// Read data sent from client
size_t buf_size = 10;
size_t data_len;
char* buf = malloc(buf_size);
// Init strings for response
struct string _content;
struct string* content = &_content;
string_init(content);
struct string _cookie;
struct string* cookie = &_cookie;
string_init(cookie);
struct string _robot_grid;
struct string* robot_grid = &_robot_grid;
string_init(robot_grid);
struct string _header;
struct string* header = &_header;
string_init(header);
// Repeat indefinitely for each new request from current client
while (1) {
// Reinitialize data_len
data_len = 0;
struct pollfd client_pollfd;
client_pollfd.fd = clientfd;
client_pollfd.events = POLLIN; // can I read?
ssize_t n = read_client(clientfd, &buf, &data_len, &buf_size);
if (n == 0) {
string_deinit(cookie);
string_deinit(content);
string_deinit(header);
string_deinit(robot_grid);
return;
}
if (n < 0) {
fprintf(
stderr, "%s:%d - read_client() failed\n", __FILE__, __LINE__);
free(buf);
string_deinit(cookie);
string_deinit(content);
string_deinit(header);
string_deinit(robot_grid);
return;
}
while (1) {
// timeout = 0 causes poll() to return immediately,
// even if no file descriptors are ready
int r = poll(&client_pollfd, 1, 0);
if (r < 0) {
perror("poll() failed");
string_deinit(cookie);
string_deinit(content);
string_deinit(header);
string_deinit(robot_grid);
return;
}
if ((client_pollfd.revents & POLLIN) == 0) {
break;
}
// Fill buf from where we stopped
n = read_client(clientfd, &buf, &data_len, &buf_size);
if (n == 0) {
break;
}
if (n < 0) {
fprintf(
stderr,
"%s:%d - read_client() failed\n", __FILE__, __LINE__);
free(buf);
break;
}
}
// Finally, last realloc to have the exact needed size and add final 0
void *tmp = realloc(buf, data_len + 1);
if (tmp == NULL) {
free(buf);
fprintf(stderr, "%s:%d - realloc() failed\n", __FILE__, __LINE__);
break;
} else {
buf = tmp;
}
buf_size = data_len + 1;
buf[data_len] = 0;
// Parse client's request
char method[16], path[1024], version[16];
int cookie_x, cookie_y, cookie_found;
parse_client_request(
buf, data_len, method, path, version, &cookie_x,
&cookie_y, &cookie_found);
// Init x_coord and y_coord
int x_coord, y_coord;
if (cookie_found) {
// init to cookie values
x_coord = cookie_x;
y_coord = cookie_y;
} else {
// No previous values, init to 0
x_coord = 0;
y_coord = 0;
}
// Declare grid size
int x_max = 4;
int y_max = 4;
// fprintf(stderr, "Coords: x=%d, y=%d\n", x_coord, y_coord);
// fprintf(stderr, "\nClient's request:\n%s\n", buf);
fprintf(stderr, "\nClient's request:\n%s %s\n", method, path);
const char* content_type;
string_clear(content);
string_clear(cookie);
int cx, cy;
// Generate response depending on request
if (
strcmp(method, "GET") == 0 &&
strcmp(path, "/data/template.css") == 0) {
// Request for CSS file
content_type = "text/css";
string_append_with_size(
content, p_tem->css_template, p_tem->css_template_size);
} else if (
strcmp(method, "GET") == 0 &&
strcmp(path, "/data/robot.png") == 0) {
// Request for robot PNG file
content_type = "image/png";
string_append_with_size(
content, p_tem->robot_png, p_tem->robot_png_size);
} else if (
strcmp(method, "GET") == 0 &&
strcmp(path, "/data/robot.js") == 0) {
// Request for robot PNG file
content_type = "application/javascript";
string_append_with_size(
content, p_tem->js_script, p_tem->js_script_size);
} else if (
strcmp(method, "GET") == 0 &&
strcmp(path,
"/.well-known/appspecific/com.chrome.devtools.json") == 0) {
// Google Chrome is to curious…
content_type = "text/html";
// Do nothing
} else if (strcmp(method, "POST") == 0 && strcmp(path, "/down") == 0) {
if (x_coord == x_max) {
x_coord = 0;
} else {
x_coord++;
}
content_type = generate_content(
x_coord, y_coord, x_max, y_max, content, robot_grid, cookie,
p_tem);
} else if (strcmp(method, "POST") == 0 && strcmp(path, "/up") == 0) {
if (x_coord == 0) {
x_coord = x_max;
} else {
x_coord--;
}
content_type = generate_content(
x_coord, y_coord, x_max, y_max, content, robot_grid, cookie,
p_tem);
} else if (
strcmp(method, "POST") == 0 && strcmp(path, "/right") == 0) {
if (y_coord == y_max) {
y_coord = 0;
} else {
y_coord++;
}
content_type = generate_content(
x_coord, y_coord, x_max, y_max, content, robot_grid, cookie,
p_tem);
} else if (
strcmp(method, "POST") == 0 && strcmp(path, "/reset") == 0) {
x_coord = 0;
y_coord = 0;
content_type = generate_content(
x_coord, y_coord, x_max, y_max, content, robot_grid, cookie,
p_tem);
} else if (strcmp(method, "POST") == 0 && strcmp(path, "/left") == 0) {
if (y_coord == 0) {
y_coord = y_max;
} else {
y_coord--;
}
content_type = generate_content(
x_coord, y_coord, x_max, y_max, content, robot_grid, cookie,
p_tem);
} else if (strcmp(method, "GET") == 0 && strcmp(path, "/") == 0) {
// Request for head page
// Do nothing
fprintf(stderr, "Request main page!\n");
content_type = generate_content(
x_coord, y_coord, x_max, y_max, content, robot_grid, cookie,
p_tem);
} else if (
strcmp(method, "GET") == 0 &&
sscanf(path, "/coords/%d/%d", &cx, &cy) == 2) {
x_coord = cx;
y_coord = cy;
content_type = generate_content(
x_coord, y_coord, x_max, y_max, content, robot_grid, cookie,
p_tem);
} else {
fprintf(stderr, "Invalid request!\n");
continue;
}
// Create header for response
int h = string_snprintf(
header,
"HTTP/1.0 200 OK\r\nContent-Type: %s\r\nContent-Length: %zu\r\n%s\r\n",
content_type, string_len(content), cookie->start);
if (h < 0) {
break;
}
// Concatenate header and data of response
struct string _str;
struct string* str = &_str;
string_init(str);
string_append_with_size(str, header->start, string_len(header));
string_append_with_size(str, content->start, string_len(content));
// Send response to client (write to client file descriptor)
ssize_t ret = write(clientfd, str->start, string_len(str));
if (ret < 0) {
perror("write() failed");
break;
}
}
string_deinit(cookie);
string_deinit(content);
string_deinit(header);
string_deinit(robot_grid);
}
/**
* @brief Creates server and accept clients' connexions.
*
* - Creates a socket and get its file descriptor.
* - Define IP address and port.
* - Attaches socket to the previously defined address and port.
* - Marks the socket as ready to receive entry connexions.
* - Reads all necessary templates files.
* - Get clients connexion address.
* - Calls handle_client().
* - Closes client's file descriptor.
*
* See https://clembytes.fr/2025/07/17/robot-episode-1-create-a-server-in-c/
* for details.
*/
int main(void) {
/*
Create a socket and get its file descriptor.
A socket is like a case number (a file descriptor)
Here, we tell him that we wanna use the IPV4 protocol family (AF_INET),
and the TCP protocol (SOCK_STREAM)
*/
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket() failed");
return 1;
}
printf("Socket created, sockfd: %d\n", sockfd);
// I always want this to avoid error when I use this twice in a row
int optval = 1;
int ret = setsockopt(
sockfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof optval);
if (ret < 0) {
perror("setsockopt() failed");
return 1;
}
// Define IP address and port
const char *interface = "0.0.0.0";
struct in_addr mysinaddr;
ret = inet_aton(interface, &mysinaddr);
if (ret == 0) {
fprintf(stderr, "Invalid IP address: %s\n", interface);
return 1;
}
struct sockaddr_in myaddr = {
.sin_family = AF_INET,
.sin_port = htons(8000),
.sin_addr = mysinaddr,
};
// Attach socket to the previously defined address and port
ret = bind(sockfd, (struct sockaddr*) &myaddr, sizeof myaddr);
if (ret < 0) {
perror("bind() failed");
return 1;
}
// Mark the socket as ready go receive entry connexions
ret = listen(sockfd, 1);
if (ret < 0) {
perror("listen() failed");
return 1;
}
// Read templates
struct templates tem;
templates_init(&tem);
// Repeat indefinitely for each new client
while (1) {
// Get client connexion address
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof client_addr;
int clientfd = accept(
sockfd, (struct sockaddr*) &client_addr, &client_addr_len);
if (clientfd < 0) {
perror("accept() failed");
return 1;
}
printf("\n --- NEW CONNEXION RECEIVED, clientfd: %d ---\n", clientfd);
handle_client(clientfd, client_addr, &tem);
// Close client file descriptor
int r = close(clientfd);
if (r < 0) {
perror("close() failed");
return 1;
}
}
templates_deinit(&tem);
}