Proxy lab

Posted joker D888

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Proxy lab相关的知识,希望对你有一定的参考价值。

CSAPP Proxy Lab

本实验需要实现一个web代理服务器,实现逐步从迭代到并发,到最终的具有缓存功能的并发代理服务器。

Web 代理是充当 Web 浏览器和终端服务器之间的中间人的程序。浏览器不是直接联系终端服务器获取网页,而是联系代理,代理将请求转发到终端服务器。当终端服务器回复代理时,代理将回复发送给浏览器。

本实验共三个部分,具体要求如下:

  • 在本实验的第一部分,您将设置代理以接受传入连接、读取和解析请求、将请求转发到 Web 服务器、读取服务器的响应并将这些响应转发到相应的客户端。第一部分将涉及学习基本的 HTTP 操作以及如何使用套接字编写通过网络连接进行通信的程序。
  • 在第二部分中,您将升级代理以处理多个并发连接。这将向您介绍如何处理并发,这是一个重要的系统概念。
  • 在第三部分也是最后一部分,您将使用最近访问的 Web 内容的简单主内存缓存将缓存添加到您的代理。

Part I

实现迭代Web代理,首先是实现一个处理HTTP/1.0 GET请求的基本迭代代理。开始时,我们的代理应侦听端⼝上的传⼊连接,端⼝号将在命令行中指定。建⽴连接后,您的代理应从客⼾端读取整个请求并解析请求。它应该判断客户端是否发送了⼀个有效的 HTTP 请求;如果是这样,它就可以建⽴自⼰与适当的 Web 服务器的连接,然后请求客⼾端指定的对象。最后,您的代理应读取服务器的响应并将其转发给客⼾端。

我们先将tiny.c中的基本框架复制过来,移除不需要的函数,保留doit,parse_uri,clienterror即可,其他还用不到,接下来我们需要修改的是doitparse_uridoit应该做的事如下:

  1. 读取客户端的请求行,判断其是否是GET请求,若不是,调用clienterror向客户端打印错误信息。
  2. parse_uri调用解析uri,提取出主机名,端口,路径信息。
  3. 代理作为客户端,连接目标服务器。
  4. 调用build_request函数构造新的请求报文new_request
  5. 将请求报文build_request发送给目标服务器。
  6. 接受目标服务器的数据,并将其直接发送给源客户端。

代码如下:

#include <stdio.h>

#include "csapp.h"

/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400

/* You won't lose style points for including this long line in your code */
static const char *user_agent_hdr =
    "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\\r\\n";
static const char *conn_hdr = "Connection: close\\r\\n";
static const char *proxy_hdr = "Proxy-Connection: close\\r\\n";

void doit(int fd);
void parse_uri(char *uri, char *hostname, char *path, int *port);
void build_request(rio_t *real_client, char *new_request, char *hostname, char *port);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);

int main(int argc, char **argv) 
    int listenfd, connfd;
    char hostname[MAXLINE], port[MAXLINE];
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;

    /* Check command line args */
    if (argc != 2) 
        fprintf(stderr, "usage: %s <port>\\n", argv[0]);
        exit(1);
    

    listenfd = Open_listenfd(argv[1]);
    while (1) 
        clientlen = sizeof(clientaddr);
        connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
        Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
        printf("Accepted connection from (%s, %s)\\n", hostname, port);
        doit(connfd);
        Close(connfd);
    


void doit(int fd) 
    int real_server_fd;
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char hostname[MAXLINE], path[MAXLINE];
    rio_t rio_client, rio_server;
    int port;

    /* Read request line and headers */
    Rio_readinitb(&rio_client, fd);                         // 初始化rio内部缓冲区
    if (!Rio_readlineb(&rio_client, buf, MAXLINE)) return;  // 读到0个字符,return
    // 请求行: GET http://www.cmu.edu/hub/index.html HTTP/1.1
    sscanf(buf, "%s %s %s", method, uri, version);

    if (strcasecmp(method, "GET")) 
        clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method");
        return;
    

    // 解析uri
    parse_uri(uri, hostname, path, &port);
    char port_str[10];
    sprintf(port_str, "%d", port);
    // 代理作为客户端,连接目标服务器
    real_server_fd = Open_clientfd(hostname, port_str);

    Rio_readinitb(&rio_server, real_server_fd);  // 初始化rio
    char new_request[MAXLINE];
    sprintf(new_request, "GET %s HTTP/1.0\\r\\n", path);
    build_request(&rio_client, new_request, hostname, port_str);

    // 向目标服务器发送http报文
    Rio_writen(real_server_fd, new_request, strlen(new_request));

    int char_nums;
    // 从目标服务器读到的数据直接发送给客户端
    while ((char_nums = Rio_readlineb(&rio_server, buf, MAXLINE))) Rio_writen(fd, buf, char_nums);


void parse_uri(char *uri, char *hostname, char *path, int *port) 
    *port = 80;  // 默认端口
    char *ptr_hostname = strstr(uri, "//");
    //  http://hostname:port/path
    if (ptr_hostname)
        ptr_hostname += 2;  // 绝对uri
    else
        ptr_hostname = uri;  // 相对uri,相对url不包含"http://"或"https://"等协议标识符

    char *ptr_port = strstr(ptr_hostname, ":");
    if (ptr_port) 
        // 字符串ptr_hostname需要以'\\0'为结尾标记
        *ptr_port = '\\0';
        strncpy(hostname, ptr_hostname, MAXLINE);

        sscanf(ptr_port + 1, "%d%s", port, path);
     else   // uri中没有端口号
        char *ptr_path = strstr(ptr_hostname, "/");
        if (ptr_path) 
            strncpy(path, ptr_path, MAXLINE);
            *ptr_path = '\\0';
            strncpy(hostname, ptr_hostname, MAXLINE);
         else 
            strncpy(hostname, ptr_hostname, MAXLINE);
            strcpy(path, "");
        
    

void build_request(rio_t *real_client, char *new_request, char *hostname, char *port) 
    char temp_buf[MAXLINE];

    // 获取client的请求报文
    while (Rio_readlineb(real_client, temp_buf, MAXLINE) > 0) 
        if (strstr(temp_buf, "\\r\\n")) break;  // end

        // 忽略以下几个字段的信息
        if (strstr(temp_buf, "Host:")) continue;
        if (strstr(temp_buf, "User-Agent:")) continue;
        if (strstr(temp_buf, "Connection:")) continue;
        if (strstr(temp_buf, "Proxy Connection:")) continue;

        sprintf(new_request, "%s%s", new_request, temp_buf);
        printf("%s\\n", new_request);
        fflush(stdout);
    
    sprintf(new_request, "%sHost: %s:%s\\r\\n", new_request, hostname, port);
    sprintf(new_request, "%s%s%s%s", new_request, user_agent_hdr, conn_hdr, proxy_hdr);
    sprintf(new_request, "%s\\r\\n", new_request);


void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) 
    char buf[MAXLINE];

    /* Print the HTTP response headers */
    sprintf(buf, "HTTP/1.0 %s %s\\r\\n", errnum, shortmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-type: text/html\\r\\n\\r\\n");
    Rio_writen(fd, buf, strlen(buf));

    /* Print the HTTP response body */
    sprintf(buf, "<html><title>Tiny Error</title>");
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf,
            "<body bgcolor="
            "ffffff"
            ">\\r\\n");
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "%s: %s\\r\\n", errnum, shortmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<p>%s: %s\\r\\n", longmsg, cause);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<hr><em>The Tiny Web server</em>\\r\\n");
    Rio_writen(fd, buf, strlen(buf));


若程序出现错误,printf大法依然是定位错误的好方法。此外可以通过使用curl来模拟操作。需要注意的是需要先运行proxy和tiny再运行curl,tiny就相当于一个目标服务器,curl则相当于一个客户端。

Part II

接下来我们需要改变上面的程序,使其可以处理多个并发请求,这里使用多线程来实现并发服务器。具体如下:

  • Accept之后通过创建新的线程来完成doit函数。
  • 注意:由于并发导致的竞争,所以需要注意connfd传入的形式,这里选择将每个已连接描述符分配到它自己的动态分配的内存块。

代码如下,只需要在Part I 基础上略作修改即可。

#include <stdio.h>

#include "csapp.h"

/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400

/* You won't lose style points for including this long line in your code */
static const char *user_agent_hdr =
    "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\\r\\n";
static const char *conn_hdr = "Connection: close\\r\\n";
static const char *proxy_hdr = "Proxy-Connection: close\\r\\n";

void *doit(void *vargp);
void parse_uri(char *uri, char *hostname, char *path, int *port);
void build_request(rio_t *real_client, char *new_request, char *hostname, char *port);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);
int main(int argc, char **argv) 
    int listenfd, *connfd;
    char hostname[MAXLINE], port[MAXLINE];
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;

    /* Check command line args */
    if (argc != 2) 
        fprintf(stderr, "usage: %s <port>\\n", argv[0]);
        exit(1);
    

    listenfd = Open_listenfd(argv[1]);

    pthread_t tid;
    while (1) 
        clientlen = sizeof(clientaddr);
        connfd = Malloc(sizeof(int));       // 给已连接的描述符分配其自己的内存块,消除竞争
        *connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
        Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
        printf("Accepted connection from (%s, %s)\\n", hostname, port);

        Pthread_create(&tid, NULL, doit, connfd);
    


void *doit(void *vargp) 
    int fd = *((int *)vargp);
    Free(vargp);
    Pthread_detach(Pthread_self());

    int real_server_fd;
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char hostname[MAXLINE], path[MAXLINE];
    rio_t rio_client, rio_server;
    int port;

    /* Read request line and headers */
    Rio_readinitb(&rio_client, fd);                         // 初始化rio内部缓冲区
    if (!Rio_readlineb(&rio_client, buf, MAXLINE)) return;  // 读到0个字符,return
    // 请求行: GET http://www.cmu.edu/hub/index.html HTTP/1.1
    sscanf(buf, "%s %s %s", method, uri, version);

    if (strcasecmp(method, "GET")) 
        clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method");
        return;
    

    // 解析uri
    parse_uri(uri, hostname, path, &port);
    char port_str[10];
    sprintf(port_str, "%d", port);
    // 代理作为客户端,连接目标服务器
    real_server_fd = Open_clientfd(hostname, port_str);

    Rio_readinitb(&rio_server, real_server_fd);  // 初始化rio
    char new_request[MAXLINE];
    sprintf(new_request, "GET %s HTTP/1.0\\r\\n", path);
    build_request(&rio_client, new_request, hostname, port_str);

    // 向目标服务器发送http报文
    Rio_writen(real_server_fd, new_request, strlen(new_request));

    int char_nums;
    // 从目标服务器读到的数据直接发送给客户端
    while ((char_nums = Rio_readlineb(&rio_server, buf, MAXLINE))) Rio_writen(fd, buf, char_nums);

    Close(fd);


void parse_uri(char *uri, char *hostname, char *path, int *port) 
    *port = 80;  // 默认端口
    char *ptr_hostname = strstr(uri, "//");
    //  http://hostname:port/path
    if (ptr_hostname)
        ptr_hostname += 2;  // 绝对uri
    else
        ptr_hostname = uri;  // 相对uri,相对url不包含"http://"或"https://"等协议标识符

    char *ptr_port = strstr(ptr_hostname, ":");
    if (ptr_port) 
        // 字符串ptr_hostname需要以'\\0'为结尾标记
        *ptr_port = '\\0';
        strncpy(hostname, ptr_hostname, MAXLINE);

        sscanf(ptr_port + 1, "%d%s", port, path);
     else   // uri中没有端口号
        char *ptr_path = strstr(ptr_hostname, "/");
        if (ptr_path) 
            strncpy(path, ptr_path, MAXLINE);
            *ptr_path = '\\0';
            strncpy(hostname, ptr_hostname, MAXLINE);
         else 
            strncpy(hostname, ptr_hostname, MAXLINE);
            strcpy(path, "");
        
    

void build_request(rio_t *real_client, char *new_request, char *hostname, char *port) 
    char temp_buf以上是关于Proxy lab的主要内容,如果未能解决你的问题,请参考以下文章

CSAPP Cache Lab

CSAPP Lab:Data Lab——第一个实验

Proxy lab

CSAPP 3e : Data lab

CSAPP Lab:Bomb Lab(从拆弹到爆炸。。。

CSAPP Bomb Lab