Proxy Lab
The Proxy Server (simply GET
not POST
) workflow can be described as:
- receive a request from the client,
- wrap the request to the destination server,
- open the file source, read the data
- write the data back to the client_fd
main()
Server Loop:
while(1){
int *client_fd_ptr = (int *)malloc(sizeof(int));
*client_fd_ptr = accept(listen_fd, NULL, NULL); // accept the request from the client.
if(*client_fd_ptr < 0){
printf("Accept Error\n");
}
printf("\nAccepted a new connection on clientfd %d\n", client_fd);
pthread_create(&tid, NULL, thread, client_fd_ptr);
}
- The server enters an infinite loop (
while(1)
), continuously accepting new client connections.
accept(listen_fd, NULL, NULL)
: This function blocks until a client connects. When a connection is accepted, it returns a new file descriptor (client_fd
) for the connection. Theaccept
function returns this file descriptor, which is assigned to a dynamically allocated integer (client_fd_ptr
).
- If
accept
fails (returns a negative value), an error message is printed.
- Upon successfully accepting a connection, the server prints a message indicating the new connection.
pthread_create(&tid, NULL, thread, client_fd_ptr);
: A new thread is created to handle the client connection. The thread functionthread()
(not shown in the code) will handle the communication with the client. The client file descriptor is passed to the thread function viaclient_fd_ptr
.
thread() as a function
void *thread(void *vargp){
pthread_detach(pthread_self());
int client_fd = *(int*)vargp;
process_request(client_fd);
close(client_fd);
free(vargp);
return NULL;
}
- The
pthread_detach(pthread_self())
function call detaches the current thread (the one executing this function) from the main thread.Detached Thread: A detached thread automatically releases its resources upon termination. This means the main thread (or any other thread) does not need to callpthread_join()
on it to clean up resources, thus avoiding memory leaks.This is useful in scenarios like this server, where each thread is independent and doesn't need to communicate back with the main thread after completing its task.
process_request(client_fd)
is a function (later discuss) that handles the actual processing of the client's request.
process_request()
process_request
, handles an HTTP request from a client, processes it, forwards the request to a server, and then sends the server's response back to the client.
Initialization:
int server_fd = -1; // Initialize server_fd to -1
char buf[MAXLINE];
const char *method, *host, *scheme, *uri, *port, *path, *http_version;
rio_t client_rio;
rio_t server_rio;
server_fd
is initialized to1
, which indicates an invalid or uninitialized state.
- Buffers and variables for handling the HTTP request and response are declared.
rio_t
is likely a structure used for buffered I/O, providing functions for reading and writing data.
Initialize the Client I/O Stream:
rio_readinitb(&client_rio, client_fd);
parser_t *p = parser_new();
rio_readinitb(&client_rio, client_fd)
initializes theclient_rio
structure to handle buffered input fromclient_fd
.
parser_new()
creates and initializes a new parser objectp
for processing the HTTP request.
Reading and Parsing the HTTP Request:
cCopy code
while ((n = rio_readlineb(&client_rio, buf, MAXLINE)) != 0) {
if (strcmp(buf, "\r\n") == 0)
break;
printf("buf = %s\n", buf);
ps = parser_parse_line(p, buf);
if (ps == HEADER) {
continue;
} else if (ps == ERROR) {
printf("Meet Error\n");
break;
}
parser_retrieve(p, METHOD, &method);
parser_retrieve(p, HOST, &host);
parser_retrieve(p, SCHEME, &scheme);
parser_retrieve(p, URI, &uri);
parser_retrieve(p, PORT, &port);
parser_retrieve(p, PATH, &path);
parser_retrieve(p, HTTP_VERSION, &http_version);
}
printf("END PARSING>...\n");
- The loop reads lines from the client's request using
rio_readlineb
.
- If an empty line (
\r\n
) is encountered, the loop breaks, indicating the end of the HTTP headers.
- Each line of the HTTP request is processed by the parser (
parser_parse_line
).
- Depending on the parser state (
ps
), different actions are taken:- If
ps
isHEADER
, it continues to the next line.
- If
ps
isERROR
, it prints an error message and breaks the loop.
- If
parser_retrieve
extracts specific components (method, host, scheme, URI, port, path, HTTP version) from the parsed request.
Connecting to the Server:
server_fd = open_clientfd(host, port);
if (server_fd < 0) { // connection failed
parser_free(p);
printf("server_fd connection error.\n");
return;
}
rio_readinitb(&server_rio, server_fd);
forward_request(server_fd, p, path);
open_clientfd(host, port)
attempts to establish a connection to the server specified byhost
andport
.
- If the connection fails (
server_fd < 0
), the parser is freed, an error message is printed, and the function returns.
- If successful, the server's response I/O stream is initialized with
rio_readinitb
, and the client's request is forwarded to the server usingforward_request
.
Handling the Server's Response:
char status_buf[MAXLINE], rubish_buf[MAXLINE];
size_t status_len = strlen("HTTP/1.0 xxx");
n = rio_readnb(&server_rio, status_buf, status_len);
if (strcmp(method, "POST") == 0) {
rio_writen(client_fd, "HTTP/1.0 501 Not Implemented\r\n", strlen("HTTP/1.0 501 Not Implemented\r\n"));
rio_readnb(&server_rio, rubish_buf, strlen("HTTP/1.0 501 Not Implemented\r\n") - status_len);
} else if (strncmp(status_buf + 9, "404", 3) == 0) {
rio_writen(client_fd, "HTTP/1.0 404 Not Found\r\n", strlen("HTTP/1.0 404 Not Found\r\n"));
rio_readnb(&server_rio, rubish_buf, strlen("HTTP/1.0 404 Not Found\r\n") - status_len);
} else {
rio_writen(client_fd, "HTTP/1.0 200 OK\r\n", strlen("HTTP/1.0 200 OK\r\n"));
rio_readnb(&server_rio, rubish_buf, strlen("HTTP/1.0 200 OK\r\n") - status_len);
}
- The server's response is read into
status_buf
.
- If the request method is
POST
, the server responds withHTTP/1.0 501 Not Implemented
, indicating that the server does not support thePOST
method.
- If the server responds with a
404 Not Found
status, the function sends a corresponding404
response to the client.
- Otherwise, it sends a
200 OK
response, indicating that the request was successful.
- The
rio_readnb
function is used to read and discard any remaining data from the server's response.
Forwarding the Remaining Response:
while ((n = rio_readnb(&server_rio, buf, MAXLINE)) != 0) {
rio_writen(client_fd, buf, n);
}
- This loop continues reading data from the server's response and forwarding it to the client until the entire response has been sent.
forward_request()
The forward_request
function is responsible for constructing an HTTP request from the parsed client request and then sending it to the server.
void forward_request(int server_fd, parser_t *p, const char *path){
header_t *h;
char req[MAXLINE];
sprintf(req, "GET /%s HTTP/1.0\r\n", path);
while ((h = parser_retrieve_next_header(p)) != NULL)
{
strcat(req, h->name);
strcat(req, ": ");
strcat(req, h->value);
strcat(req, "\r\n");
}
if(vbs)printf("Forward Request: \n%s\n", req);
rio_writen(server_fd, req, strlen(req));
rio_writen(server_fd, "\r\n", 2);
}
Function Parameters:
int server_fd
: The file descriptor for the connection to the server. This is where the constructed HTTP request will be sent.
parser_t *p
: A pointer to a parser object that holds the parsed HTTP headers from the client's request.
const char *path
: The path portion of the URL requested by the client.
Constructing the Initial HTTP Request Line:
char req[MAXLINE];
sprintf(req, "GET /%s HTTP/1.0\r\n", path);
- The HTTP request is initially constructed with the
GET
method, the path extracted from the client's request, and the HTTP/1.0 version.
sprintf
is used to format this string and store it in thereq
buffer.
- Example: If
path
is "index.html",req
will initially contain:"GET /index.html HTTP/1.0\r\n"
.
Adding the HTTP Headers:
while ((h = parser_retrieve_next_header(p)) != NULL)
{
strcat(req, h->name);
strcat(req, ": ");
strcat(req, h->value);
strcat(req, "\r\n");
}
- The function retrieves headers from the parsed request using
parser_retrieve_next_header
.
- For each header, it appends the header name, a colon, the header value, and a newline (
\r\n
) to thereq
buffer.
- This loop continues until all headers have been retrieved and added to the request.
- Example: If there is a header
Host: www.example.com
, it will be appended toreq
, making it look like:"GET /index.html HTTP/1.0\r\nHost: www.example.com\r\n"
.
Sending the Request to the Server:
rio_writen(server_fd, req, strlen(req));
rio_writen(server_fd, "\r\n", 2);
- The constructed HTTP request (
req
) is sent to the server usingrio_writen
, which writes the data to theserver_fd
.
- The function then sends an additional blank line (
\r\n
), which marks the end of the HTTP headers.
Now, the Proxy lab is finished.