# 2020q3 期末開發紀錄 Proxy Lab contributed by < `fdfdd12345628` > Github 連結:[proxylab](https://github.com/fdfdd12345628/proxylab) ## 作業需求 這份作業是 CS:APP3e 中的一個 lab 需要先閱讀課本的第11章再來做這份作業 ### Proxy 基本認識 使用者透過proxy來與server聯絡,而server也是透過proxy來回復 使用proxy有以下優點: 1. 確保server不會知道使用者資訊 2. 可以當作防火牆防止使用者被攻擊 3. 可以快取靜態資料,以便下次使用時不用再送request給server 4. 存取特定ip才能存取的資料 ### 作業 part 1: Implementing a sequential web proxy 實作一個可以處理HTTP/1.0 GET的proxy,其他method例如POST為選擇性 其中必須要接受如下的 request `GET http://www.cmu.edu/hub/index.html HTTP/1.1` 並且轉換成如下的 request `GET /hub/index.html HTTP/1.0` 並且包含下列 header 1. `Host: www.cmu.edu` 2. `User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3` 3. `Connection: close` 4. `Proxy-Connection: close` 剩下的header全部原封不動的複製一份 可以在命令列就指定 listen port `linux> ./proxy 15213` 首先先安裝會用到的工具 `sudo apt install build-essentials net-tools curl` 以及交作業需要用到的 git 等工具 接著參考 `tiny.c` 內部的架構進行修改,其中 `main` 的架構基本一樣,因此先從 `doit` 下手,由於 `doit` 不只要接收 connection 也要發起 connection 。因此在接受後,也要呼叫 `Open_clientfd` 來連接 end server ```clike= void doit(int fd) { int is_static; struct stat sbuf; char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE]; char filename[MAXLINE], cgiargs[MAXLINE]; rio_t rio; /* Read request line and headers */ Rio_readinitb(&rio, fd); if (!Rio_readlineb(&rio, buf, MAXLINE)) //line:netp:doit:readrequest return; printf("%s", buf); sscanf(buf, "%s %s %s", method, uri, version); if (strcasecmp(method, "GET")) { //line:netp:doit:beginrequesterr clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method"); return; } int end_seerver_fd = Open_clientfd(hostname, port); } ``` 這裡發現需要 hostname 與 port ,因此必須改寫 parse_uri 來將 hostname, port, path 等資訊分開 ```clike= *port = "80"; char *http_pos = strstr(uri, "//"); char *port_pos = strstr(http_pos + 2, ":"); char *path_pos = strstr(http_pos + 2, "/"); printf("%s", path_pos); char *end_pos = strstr(path_pos + 1, " "); printf("%s", end_pos); strncpy(hostname, http_pos + 2, (int)(strlen(http_pos + 2) - strlen(port_pos))); hostname[(int)(strlen(http_pos + 2) - strlen(port_pos))] = '\0'; strncpy(path, path_pos, (int)(strlen(path_pos) - strlen(end_pos))); path[(int)(strlen(path_pos) - strlen(end_pos))] = '\0'; strncpy(port, port_pos + 1, (int)(strlen(port_pos + 1) - strlen(path_pos))); port[(int)(strlen(port_pos + 1) - strlen(path_pos))] = '\0'; return 1; ``` 由於 strncpy 並不會加上字串結尾 `\0` 因此這裡手動加上 繼續回頭處理 `doit` ```clike= void doit(int fd) { int is_static; struct stat sbuf; char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE]; char filename[MAXLINE], cgiargs[MAXLINE]; rio_t rio; /* Read request line and headers */ Rio_readinitb(&rio, fd); if (!Rio_readlineb(&rio, buf, MAXLINE)) //line:netp:doit:readrequest return; printf("%s", buf); sscanf(buf, "%s %s %s", method, uri, version); //line:netp:doit:parserequest if (strcasecmp(method, "GET")) { //line:netp:doit:beginrequesterr clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method"); return; } //line:netp:doit:endrequesterr //read_requesthdrs(&rio); //line:netp:doit:readrequesthdrs int end_server_fd; char *port[6], hostname[MAXLINE], path[MAXLINE]; char *request[MAXLINE]; parse_uri(buf, hostname, port, path); end_server_fd = Open_clientfd(hostname, port); rio_t end_server_rio; Rio_readinitb(&end_server_rio, end_server_fd); sprintf(request, "%s%s%s", "GET ", path, " HTTP/1.0\r\n\r\n"); //Rio_writen(end_server_fd, "GET ", 4); //Rio_writen(end_server_fd, path, strlen(path)); //Rio_writen(end_server_fd, " HTTP/1.0\r\n\r\n", strlen(" HTTP/1.0\r\n\r\n")); Rio_writen(end_server_fd, request, strlen(request)); size_t n; while ((n = Rio_readlineb(&end_server_rio, buf, MAXLINE)) != 0) { Rio_writen(fd, buf, n); } // Rio_readlineb(&end_server_rio, buf, MAXLINE); // rio_readn(&end_server_rio, buf, MAXLINE); // printf("%s\n", buf); Close(end_server_fd); } ``` 將 request 送到 end server ,再用 `Rio_readlineb` 讀取 server response 後傳給 user 接著處理header ```clike= void read_requesthdrs(rio_t *rp, char *dest, char *hostname) { char buf[MAXLINE]; Rio_readlineb(rp, buf, MAXLINE); // printf("%s", buf); int is_host = 0; char *host = "Host: "; int is_ua = 0; char *user_agent = "User-Agent: "; int is_connection = 0; char *connection = "Connection: "; int is_proxy_connection = 0; char *proxy_connection = "Proxy-Connection: "; char *headers[MAXLINE]={0}; char *headers_pointer=headers; while (strcmp(buf, "\r\n")) { //line:netp:readhdrs:checkterm if (strstr(buf, host)) { strncpy(headers_pointer, buf, strlen(buf)); headers_pointer+=strlen(buf); is_host = 1; } else if (strstr(buf, user_agent)) { strncpy(headers_pointer, buf, strlen(buf)); headers_pointer+=strlen(buf); is_ua = 1; } else if (strstr(buf, connection)) { // strncpy(headers, buf, strlen(buf)); // is_host = 1; } else if (strstr(buf, proxy_connection)) { // strncpy(headers, buf, strlen(buf)); // is_host = 1; } else { strncpy(headers_pointer, buf, strlen(buf)); headers_pointer+=strlen(buf); } Rio_readlineb(rp, buf, MAXLINE); // printf("%s", buf); } if(!is_host){ strncpy(headers_pointer, host, strlen(host)); headers_pointer+=strlen(host); strncpy(headers_pointer, hostname, strlen(hostname)); headers_pointer+=strlen(hostname); strncpy(headers_pointer, "\r\n", 2); headers_pointer+=2; } if(!is_ua){ strncpy(headers_pointer, user_agent, strlen(user_agent)); headers_pointer+=strlen(user_agent); strncpy(headers_pointer, user_agent_hdr, strlen(user_agent_hdr)); headers_pointer+=strlen(user_agent_hdr); strncpy(headers_pointer, "\r\n", 2); headers_pointer+=2; } if(!is_connection){ strncpy(headers_pointer, connection, strlen(connection)); headers_pointer+=strlen(connection); strncpy(headers_pointer, "close", 5); headers_pointer+=5; strncpy(headers_pointer, "\r\n", 2); headers_pointer+=2; } if(!is_proxy_connection){ strncpy(headers_pointer, proxy_connection, strlen(proxy_connection)); headers_pointer+=strlen(proxy_connection); strncpy(headers_pointer, "close", 5); headers_pointer+=5; strncpy(headers_pointer, "\r\n", 2); headers_pointer+=2; } strncpy(headers_pointer, "\r\n", 2); headers_pointer+=2; strncpy(dest, headers, strlen(headers)); return; } ``` 如果 Host 與 User-Agent 存在,則使用 user 傳過來的,否則自己產生。 Connection 與 Proxy-Connection 無論如何都用自己的。 剩下的原封不動複製過去 因此 `doit` 要再多傳輸 headers ```clike= void doit(int fd) { ... rio_t end_server_rio; Rio_readinitb(&end_server_rio, end_server_fd); sprintf(request, "%s%s%s%s", "GET ", path, " HTTP/1.0\r\n", headers); Rio_writen(end_server_fd, request, strlen(request)); ... } ``` ### 作業 part 2: Dealing with multiple concurrent requests 參考 CS:APP 第十二章內的 `echoserverp.c` 對 `int main()` 進行修改 ```clike= int main(int argc, char **argv) { int listenfd, connfd; char hostname[MAXLINE], port[MAXLINE], path[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); //line:netp:tiny:accept if(Fork()==0){ Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0); printf("Accepted connection from (%s, %s)\n", hostname, port); Close(listenfd); doit(connfd); Close(connfd); exit(0); } // doit(connfd); //line:netp:tiny:doit // Close(connfd); //line:netp:tiny:close Close(connfd); } } ``` ### 作業 part 3: Caching web objects