Before I had AI tools, I never intended to learn the C language. I had no foundation in this area because I believed it was difficult and time-consuming. As someone who mainly used Python, I understood that to improve my skills, learning C was necessary. Therefore, I recently installed Cxxdroid and decided to give C programming a try.
First, I asked AI to help me write an HTTP server in C language. It was very simple, and the syntax of the code looked similar to JavaScript, except with many strict rules.
Next, I requested AI to add new functionalities to the foundation. It almost covered all the basic features, including playing various multimedia files, pausing and selecting playback positions, handling various mimetypes, and forwarding certain requests to a fastapi server, which may be difficult to achieve using pure C language. The final code was only 300 lines long and was easy to understand.
During this process, I had a great experience using Cxxdroid. I barely noticed any delay in compilation, similar to writing Python code using Pydroid3.
Without learning the basic syntax and knowledge, I relied entirely on AI to write the code and make it practical very quickly. The most important aspects in this process were testing and persistence in conceptual ideas. Currently, the implementation of the idea is quite rudimentary, leaving plenty of room for improvement. Through this process, I gradually acquired some knowledge of the C language. All this knowledge is necessary to learn, but with AI's help, I can practice and apply it in a shorter period of time, greatly accelerating the process from practice to application.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/wait.h>
#define MAX_EVENTS 10
#define DEFAULT_PORT 8001
#define MAX_REQUEST_LEN 1024
#define MAX_RESPONSE_LEN 1024
#include <curl/curl.h>
int min(int a, int b){
if(a < b)
return a;
else
return b;
}
char* read_until(int fd, char delimiter) {
char* data = NULL; // Initialize a null pointer to store the read data
char* tmp = (char*) malloc(2 * sizeof(char)); // Create a temporary buffer to read one character at a time
int i = 0;
while (1) {
int result = read(fd, tmp, 1); // Read one character from the input stream
if (result <= 0) {
// If the end of the input stream is reached, return the data that has been read
free(tmp);
return data;
}
if (tmp[0] == delimiter) {
// If the temporary buffer contains the specified delimiter, exit the loop
break;
}
data = (char*) realloc(data, (i + 2) * sizeof(char)); // Extend the data buffer to store the new character
data[i] = tmp[0]; // Append the new character to the data buffer
data[i + 1] = '\0'; // Terminate the data buffer with a null character
i++;
}
free(tmp);
return data;
}
char* get_file_type(const char* file_path) {
const char* file_type = "text/plain";
const char* ext = strrchr(file_path, '.');
if (ext != NULL) {
if (strcmp(ext, ".html") == 0) {
file_type = "text/html";
} else if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) {
file_type = "image/jpeg";
} else if (strcmp(ext, ".gif") == 0) {
file_type = "image/gif";
} else if (strcmp(ext, ".png") == 0) {
file_type = "image/png";
} else if (strcmp(ext, ".css") == 0) {
file_type = "text/css";
} else if (strcmp(ext, ".mp4") == 0) {
file_type = "video/mp4";
} else if (strcmp(ext, ".mkv") == 0) {
file_type = "video/x-matroska";
} else if (strcmp(ext, ".flac") == 0) {
file_type = "audio/flac";
} else if (strcmp(ext, ".js") == 0) {
file_type = "application/javascript";
} else if (strcmp(ext, ".json") == 0) {
file_type = "application/json";
} else if (strcmp(ext, ".mp3") == 0) {
file_type = "audio/mpeg";
} else if (strcmp(ext, ".m3u") == 0) {
file_type = "audio/mpegurl";
} else if (strcmp(ext, ".pdf") == 0) {
file_type = "application/pdf";
} else if (strcmp(ext, ".doc") == 0 || strcmp(ext, ".docx") == 0) {
file_type = "application/msword";
} else if (strcmp(ext, ".xls") == 0 || strcmp(ext, ".xlsx") == 0) {
file_type = "application/vnd.ms-excel";
} else if (strcmp(ext, ".ppt") == 0 || strcmp(ext, ".pptx") == 0) {
file_type = "application/vnd.ms-powerpoint";
} else if (strcmp(ext, ".txt") == 0) {
file_type = "text/plain";
} else if (strcmp(ext, ".zip") == 0) {
file_type = "application/zip";
} else if (strcmp(ext, ".svg") == 0) {
file_type = "image/svg+xml";
} else if (strcmp(ext, ".xml") == 0) {
file_type = "application/xml";
} else if (strcmp(ext, ".csv") == 0) {
file_type = "text/csv";
} else if (strcmp(ext, ".md") == 0) {
file_type = "text/markdown";
} else if (strcmp(ext, ".avi") == 0) {
file_type = "video/x-msvideo";
} else if (strcmp(ext, ".wmv") == 0) {
file_type = "video/x-ms-wmv";
} else if (strcmp(ext, ".mov") == 0) {
file_type = "video/quicktime";
} else if (strcmp(ext, ".wav") == 0) {
file_type = "audio/wav";
} else if (strcmp(ext, ".ogg") == 0) {
file_type = "audio/ogg";
} else if (strcmp(ext, ".ttf") == 0) {
file_type = "font/ttf";
} else if (strcmp(ext, ".otf") == 0) {
file_type = "font/otf";
}
}
return (char*)file_type;
}
typedef struct {
char* method;
char* path;
int content_length;
} HttpRequest;
typedef struct {
int status_code;
char reason_phrase[32];
char content_type[32];
int content_length;
int header_length;
} HttpResponse;
// Parse an HTTP request message
HttpRequest parse_request(char* request) {
HttpRequest http_request;
char* p = request;
// Parse the method and path
http_request.method = strndup(request, strcspn(request, " "));
p += strlen(http_request.method) + 1;
http_request.path = strndup(p, strcspn(p, " "));
p += strlen(http_request.path) + 1;
// Parse the content length (if present)
char* content_length_header = strstr(request, "Content-Length:");
if (content_length_header != NULL) {
http_request.content_length = atoi(content_length_header + strlen("Content-Length:"));
} else {
http_request.content_length = 0;
}
return http_request;
}
// Parse an HTTP response message
HttpResponse parse_response(char* response) {
HttpResponse http_response = {0};
char* p = response;
// Parse the status code and reason phrase
sscanf(p, "HTTP/1.1 %d %[^\r\n]", &(http_response.status_code), http_response.reason_phrase);
// Skip the status line and parse the headers
p = strchr(p, '\n') + 1;
while (1) {
char key[32], value[32];
if (sscanf(p, "%[^:]: %[^\r\n]\r\n", key, value) == 2) {
if (strcasecmp(key, "Content-Type") == 0) {
strncpy(http_response.content_type, value, sizeof(http_response.content_type) - 1);
} else if (strcasecmp(key, "Content-Length") == 0) {
http_response.content_length = atoi(value);
}
p = strchr(p, '\n') + 1;
}
else {
// We found the end of the headers
http_response.header_length = p - response;
break;
}
}
return http_response;
}
void handle_client_request(int client_fd) {
char request[MAX_REQUEST_LEN] = { 0 };
char response[MAX_RESPONSE_LEN] = { 0 };
int valread;
// Read the request from the client
valread = read(client_fd, request, MAX_REQUEST_LEN);
if (valread == -1) {
perror("read");
return;
}
// Parse the request
HttpRequest httpRequest = parse_request(request);
// Get the path from the request
char* path = httpRequest.path;
// If the request is for the root directory, return the content of "static/chat.html"
printf("%s ", path);
if (strcmp(path, "/") == 0) {
// Open the file "static/chat.html"
FILE* fp = fopen("static/chat.html", "rb");
if (fp == NULL) {
printf("404\n");
// If the file does not exist, return a 404 error
const char* response_header_fmt = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 9\r\n\r\n";
const char* response_body = "Not found";
sprintf(response, "%s%s", response_header_fmt, response_body);
write(client_fd, response, strlen(response));
shutdown(client_fd, SHUT_WR);
close(client_fd);
}
else {
// Get the file size
printf("200\n");
int file_size;
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
rewind(fp);
char* file_buffer = (char*)malloc(MAX_RESPONSE_LEN);
// Return the entire file content
const char* response_header_fmt = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n";
sprintf(response, response_header_fmt, file_size);
write(client_fd, response, strlen(response));
// read and send the file in chunks of MAX_RESPONSE_LEN
while (file_size > 0) {
int chunk_size = file_size > MAX_RESPONSE_LEN ? MAX_RESPONSE_LEN : file_size;
fread(file_buffer, 1, chunk_size, fp);
write(client_fd, file_buffer, chunk_size);
file_size -= chunk_size;
}
free(file_buffer);
fclose(fp);
shutdown(client_fd, SHUT_WR);
close(client_fd);
}
}
else if (strncmp(path, "/static/", 8) == 0) {
// If the request is for a file under "/static/", try to read the file
FILE* fp;
int file_size;
char* file_buffer;
const char* response_header_fmt = "HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %d\r\nAccept-Ranges: bytes\r\n\r\n";
// Make sure the file exists
char file_path[MAX_REQUEST_LEN] = { 0 };
memmove(path, path + 1, strlen(path));
fp = fopen(path, "rb");
if (fp == NULL) {
printf("404\n");
// If the file does not exist, return a 404 error
const char* response_header_fmt = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 9\r\n\r\n";
const char* response_body = "Not found";
sprintf(response, "%s%s", response_header_fmt, response_body);
write(client_fd, response, strlen(response));
shutdown(client_fd, SHUT_WR);
close(client_fd);
}
else {
// Get the file type and size
printf("200\n");
const char* file_type = get_file_type(path);
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
rewind(fp);
file_buffer = (char*)malloc(MAX_RESPONSE_LEN);
// Check if the request includes a range header
char* range_header = strstr(request, "Range: bytes=");
if (range_header != NULL) {
long range_start, range_end;
int range_length;
if (sscanf(range_header, "Range: bytes=%ld-%ld", &range_start, &range_end) == 2) {
// Calculate the length of the requested range
range_length = range_end - range_start + 1;
// Set the response header to include the range length
printf("Range : %ld - %ld\n", range_start, range_end);
sprintf(response, response_header_fmt, file_type, range_length);
write(client_fd, response, strlen(response));
// Set the pointer to read a part of the file and return it
fseek(fp, range_start, SEEK_SET);
while (range_length > 0) {
int chunk_size = range_length > MAX_RESPONSE_LEN ? MAX_RESPONSE_LEN : range_length;
fread(file_buffer, 1, chunk_size, fp);
write(client_fd, file_buffer, chunk_size);
range_length -= chunk_size;
}
free(file_buffer);
fclose(fp);
shutdown(client_fd, SHUT_WR);
close(client_fd);
return;
}
}
// Return the entire file content
sprintf(response, response_header_fmt, file_type, file_size);
write(client_fd, response, strlen(response));
// read and send the file in chunks of MAX_RESPONSE_LEN
while (file_size > 0) {
int chunk_size = file_size > MAX_RESPONSE_LEN ? MAX_RESPONSE_LEN : file_size;
fread(file_buffer, 1, chunk_size, fp);
write(client_fd, file_buffer, chunk_size);
file_size -= chunk_size;
}
free(file_buffer);
fclose(fp);
shutdown(client_fd, SHUT_WR);
close(client_fd);
}
}
else {
printf("forwarded to localhost:8000\n");
int server_fw = socket(AF_INET, SOCK_STREAM, 0);
if (server_fw < 0) {
perror("socket");
return;
}
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8000);
inet_pton(AF_INET, "127.0.0.1", &(server_addr.sin_addr));
if (connect(server_fw, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
close(server_fw);
return;
}
int content_length = httpRequest.content_length;
int sent_length = 0;
if (write(server_fw, request, strlen(request)) < 0) {
perror("write");
close(server_fw);
return;
}
sent_length += strlen(request);
// If there's remaining data in client_fd
char buf[MAX_REQUEST_LEN];
while (sent_length < content_length) {
int len = read(client_fd, buf, min(content_length - sent_length,MAX_REQUEST_LEN ));
if (len < 0) {
perror("read");
break;
}
if (len == 0) {
break;
}
if (write(server_fw, buf, len) != len) {
perror("write");
break;
}
sent_length += len;
}
char response_buf[MAX_RESPONSE_LEN];
int valread;
while (1) {
valread = read(server_fw, response_buf, MAX_RESPONSE_LEN);
if (valread <= 0) {
break;
}
write(client_fd, response_buf, valread);
}
shutdown(server_fw, SHUT_RDWR);
close(server_fw);
shutdown(client_fd, SHUT_WR);
close(client_fd);
}
}
int main(int argc, char* argv[]) {
struct sockaddr_in address;
int opt = 1;
int server_fd;
int client_fd;
pid_t pid;
// Create server socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
// Enable port reuse
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
// Bind address and port number
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_port = htons(DEFAULT_PORT);
bind(server_fd, (struct sockaddr *)&address, sizeof(address));
int flags = fcntl(server_fd, F_GETFL, 0);
fcntl(server_fd, F_SETFL, flags | O_NONBLOCK);
printf("Host is: %s, Port is: %d\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port)); // Print host and port.
// Listen for connection requests from client
listen(server_fd, 10);
while (1) {
// Accept client request
struct sockaddr_in client_address;
socklen_t client_length = sizeof(client_address);
client_fd = accept(server_fd, (struct sockaddr *)&client_address, &client_length);
if (client_fd == -1) {
//perror("accept");
continue;
}
printf("Client IP is: %s, Port is: %d\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port)); // Print client ip and port.
// Create child process to handle client request, while parent process continues to listen
pid = fork();
if (pid == -1) {
perror("fork");
close(client_fd);
} else if (pid == 0) {
// Child process
handle_client_request(client_fd);
}
}
// Close the server_fd connection
//close(server_fd);
return 0;
}
沒有留言:
發佈留言