# 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