Proxy Lab

The Proxy Server (simply GET not POST) workflow can be described as:

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);
}

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;
}

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;

Initialize the Client I/O Stream:

rio_readinitb(&client_rio, client_fd);
parser_t *p = parser_new();

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");

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 by host and port.
  • 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 using forward_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 with HTTP/1.0 501 Not Implemented, indicating that the server does not support the POST method.
  • If the server responds with a 404 Not Found status, the function sends a corresponding 404 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 the req 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 the req 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 to req, 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 using rio_writen, which writes the data to the server_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.