实现一个简单的Proxy(代理)

简单地解释一下什么是代理服务器。代理服务器的实现是一种软件,或“伪目标服务器”,你的程序会将其数据发送到该服务器上。

伪目标服务器的配置方式有两种:

可见的方式,这就是我们要实现的一个简单的Proxy方式。你的程序知道,并同意将其所有数据发送到代理服务器,而不是原来的目标服务器,并让代理做进一步的连接。不可见的方式,即“透明代理”。这类代理可以部署在网关设备(如路由器)上,而此类设备将把您的目标服务器更改为配置的代理服务器。你的程序,甚至你自己,都不会注意到目标服务器发生了更改。这种方式的一个实际用例是负载均衡器:你有两个web服务器承载相同的web页面,你希望将访问者平衡到不同的服务器,以提高响应时间。你所能做的就是建立一个“反向代理”,让你所有的访问者访问这个“反向代理”的IP,这个代理将选择它要连接的服务器。如果配置,浏览器和后端服务器都不会注意到负载均衡器的存在。

代理服务器作为客户端和服务器的中间人,既需要我们了解客户端的知识,也需要懂得服务器的知识。整个过程如下:

客户端发送请求给代理服务器代理服务器转发请求给实际访问的服务器实际访问的服务器返回响应给代理服务器代理服务器转发响应给客户端

开启服务器

bind: 请求 内核 把 socket address 和 socket descriptor 绑定

getaddrinfo: 解析服务器所需要的信息, 这个函数主要是来通过 DNS 获取 server 主机的DNS信息,比如 ip 地址,别名之类的,返回的是一个 struct 的指针。但是这个 struct 是一个静态变量,也就是说这些函数不支持多线程的访问,是线程不安全的。解决方法是定义一个 mutex 来加锁,任意时刻只能又一个线程在调这些函数。

下一步:

打开 listen端口 : 做好接收请求的准备(listen端口一旦打开,只有等到服务器关闭的适合,它才会关闭,不然它会一只等待着接受请求)

accept: 等待连接

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

解析uri:

Hostname: 去除前面的 https:// 或 http://

Port: 如果端口为NULL, 使用默认WEB: 80端口,否则额外端口 + 1

Path: 取出路径的部分,例如 "/index.html"

/* * Parse uri * Get hostname, path, port */ void parse_uri(char* uri, char* hostname, char* path, char* port) { char *get_host; char *get_path; char *get_port; /* Get host */ get_host = strstr(uri, "//") + 2; /* Get path */ get_path = strchr(get_host, /); if(get_path != NULL){ strcpy(path, get_path); strcpy(get_path, ""); } else{ strcpy(path, "/"); } /* Get port */ get_port = strchr(get_host, :); if(get_port != NULL){ strcpy(port, get_port + 1); strcpy(get_port, ""); } else{ strcpy(port, "80"); } strcpy(hostname, get_host); return; }

请求header并发送给客户端:

User-Agent: 如 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/ Firefox/10.0.3

Connection: 必须发送 Connection: close

Proxy-Connection: 必须发送 Proxy-Connection: close

/* * Get headers and sent it to the client server */ void requesthdrs(char *headers, rio_t *rp, int clientfd, char *hostname, char *path) { char get_hdr[MAXLINE]; char get_host[MAXLINE]; sprintf(get_hdr, "GET %s HTTP/1.0\r\n", path); sprintf(get_host, "Host: %s\r\n", hostname); strcpy(headers, get_hdr); strcat(headers, get_host); strcat(headers, user_agent_hdr); strcat(headers, connection_hdr); strcat(headers, proxy_connetion_hdr); strcat(headers, emptyline); rio_writen(clientfd, headers, strlen(headers)); return; }

客户端:

doit函数中对于客户端请求的header,需要判断是否是请求类型(GET)。然后加入uri函数进行分析,获取需要连接的服务器的hostname,port。修改客户端的HTTP Header,让proxy充当客户端将信息转发给正确的服务器,接受服务器的返回并转发给正真的请求客户端。

/* * Parse http request * Parse uri * Request headers * Connect to client * Server read files */ void doit(int fd, Node *node){ char buf[MAXLINE]; char method[MAXLINE]; char uri[MAXLINE]; char version[MAXLINE]; char hostname[MAXLINE]; char port[MAXLINE]; char path[MAXLINE]; char headers[MAXLINE]; char content[MAX_OBJECT_SIZE]; int size; int sum = 0; int clientfd; size_t n; rio_t rio, rp; strcpy(content, ""); rio_readinitb(&rio, fd); if (!rio_readlineb(&rio, buf, MAXLINE)) return; printf("%s", buf); sscanf(buf, "%s %s %s", method, uri, version); if (strcasecmp(method, "GET")){ clienterror(fd, method, "501", "Not Implemented", "Proxy does not implement this method"); return; } /* Parse url */ parse_uri(uri, hostname, path, port); /* Search Cache */ if(cacheSearch(node, hostname, path, port, fd)){ return; } /* Connecting to the end server */ clientfd = Open_clientfd(hostname, port); rio_readinitb(&rp, clientfd); /* Request headers */ requesthdrs(headers, &rio, clientfd, hostname, path); /* Receive message from end server */ while((n = rio_readnb(&rp, buf, MAXLINE)) != 0){ printf("server received %zu bytes\n", n); rio_writen(fd, buf, n); strcpy(content, buf); sum += n; } size = sum; /* Add cache */ Block *new_block = (Block*) malloc(sizeof(Block)); new_block = initCache(hostname, path, port, content); addCache(node, new_block, size); Close(clientfd); }

缓存部分

/* * Insert cache at head */ void insertHead(Node *node, Block *data){ Block *newNode = (Block*) malloc(sizeof(Block)); strcpy(newNode -> hostname, data -> hostname); strcpy(newNode -> path, data -> path); strcpy(newNode -> port, data -> port); strcpy(newNode -> content, data -> content); newNode -> next = node -> head; newNode -> prev = NULL; if(node -> head == NULL){ node -> head = newNode; node -> tail = newNode; } if(node -> head != NULL){ (node -> head) -> prev = newNode; } node -> head = newNode; } /* * Remove cache at tail */ void removeTail(Node *node){ Block *newTail = NULL; if(node -> head == node -> tail){ newTail = node -> tail; free(newTail); node -> head = NULL; node -> tail = NULL; } if(node -> head != NULL){ newTail = node -> tail; node -> tail = newTail -> prev; free(newTail); (node -> tail) -> next = NULL; } } /* * Check the maximum size and add cache */ int addCache(Node *node, Block *data, int size){ node -> size = node -> size + size; if(node -> size <= MAX_CACHE_SIZE){ if(size <= MAX_OBJECT_SIZE){ insertHead(node, data); } } while(node -> size > MAX_CACHE_SIZE){ printf("Remove Node\n"); removeTail(node); node -> size = node -> size - MAX_OBJECT_SIZE; insertHead(node, data); } printf("Total size: %d\n", node -> size); return 0; } /* * If the cache hit, return the data to the server * If the cache miss, connect to the server and request for data */ Block* cacheSearch(Node *node, char *hostname, char *path, char *port, int fd){ Block *pointer = node -> head; while(pointer){ if(!strcmp(pointer -> hostname, hostname) && !strcmp(pointer -> path, path) && !strcmp(pointer -> port, port)){ rio_writen(fd, pointer->content, strlen(pointer->content)); printf("Cache Hit\n"); return pointer; } pointer = pointer -> next; } return NULL; }