Http代理服务器

一、计算机网络之HTTP代理服务器

注:参考哈工大计算机网络指导书

源码下载地址

实验的内容:
1.实现基本的HTTP代理服务器,可以在指定端口接收客户端http的请求并且根据其中个URL地址访问该地址所指向的HTTP服务器,接收服务器的响应报文,并将响应报文转发给对应的客户进行浏览。
2.设计并实现一个支持cache功能的HTTP代理服务器,要求能缓存原服务器响应的对象,并能够通过修改请求报文(添加 if-modified-since头行),向原服务器确认缓存对象是否是最新版本
3.扩展 HTTP 代理服务器,支持如下功能:
a、网站过滤:允许/不允许访问某些网站
b、用户过滤:支持/不支持某些用户访问外部网站
c、网站引导:将用户对某个网站的访问引导至一个模拟网站(钓鱼)

代理服务器,俗称 “翻墙软件” ,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。如图所示,为普通 Web 应用通信方式与采用代理服务器的通信方式的对比。

实现的是多用户代理服务器,实现线程的并行执行。 首先,代理服务器创建 HTTP 代理服务的 TCP 主套接字,通过该主套接字监听等待客户端的连接请求。当客户端连接之后,创建一个子线程,由子线程执行上述一对一的代理过程,服务结束之后子线程终止。与此同时,主线程继续接受下一个客户的代理服务。

实验环境配置

打开IE(windows)-> 工具 -> Internet选项 -> 连接 -> 局域网设置 -> 代理服务器
如下设置地址和端口号(端口号和程序中保持一致即可(最好使用较大的,低端口一般被占用))


基本的HTTP代理服务器,实现简单,不用处理直接转发接收到目的服务器的报文即可。因此不作介绍,主要讲解实现的扩展功能

1网站过滤的实现
基本思想:通过解析客户端请求的buffer,获取请求的URL,然后设置关键字,再次使用c++中的一个函数strstr,判断关键字是否在其子串中

bool ForbiddenToConnect(char *httpheader)
{
    char * forbiddernUrl = ".edu.cn"; //屏蔽的含有关键字的网址
    if (strstr(httpheader, forbiddernUrl)!=NULL) //是否含有屏蔽的关键字
    {
        return false;
    }
    else return true;
}


2用户过滤的实现
基本思想:通过客户端请求,获取请求用户的IP,然后在程序运行时,设置一个IP禁止表,在IP禁止表中存入一些IP地址,在请求之后就去查询是否在IP禁止表中,如果是,则关闭socket即可,等待下一次的请求。

char client_IP[16];
//设置禁用IP,可以在此添加更多
memcpy(ForbiddenIP[IPnum++], "127.0.0.1", 16);
//设置访问哪些网站会被重定向到钓鱼网站
memcpy(fishUrl[fishUrlnum++], "http://www.asus.com.cn/",23);
memcpy(fishUrl[fishUrlnum++],"http://pku.edu.cn/",18);
while (true) {
    int ff;
    ff = sizeof(acceptAddr);
    acceptSocket = accept(ProxyServer, (SOCKADDR*)&acceptAddr,&(ff));
    printf("获取用户IP地址:%s\n",inet_ntoa(acceptAddr.sin_addr));
    memcpy(client_IP, inet_ntoa(acceptAddr.sin_addr),16);//用户IP保存
    //禁用用户IP访问
    if (UserIsForbidden(client_IP))
    {
        printf("IP被禁用\n");
        closesocket(acceptSocket);
        //system("pause");
        //break;
    }


重点是

acceptSocket = accept(ProxyServer, (SOCKADDR*)&acceptAddr,&(ff));


获取请求IP的方法。如果最后一个参数设置的是0,则返回的IP一直是204.204.204.204,显然不是请求的IP地址,因此需要作上述处理,先获取acceptAddr的大小,然后其地址作为accept函数的地址

3钓鱼网站的实现(钓鱼网站)
基本思想:设置一个钓鱼网站的URL表,如果请求的URL在此URL表中,则丢弃其的请求报文,转成302报文发送,在302报文中定位到钓鱼网站的网址,从而实现访问请求网站自动重定向到钓鱼网站

//网站引导  访问pku.edu.cn  asus.com 重定向到 today.hit.edu.cn
if (GotoFalseWebsite(httpHeader->url))
{   //302报文 对客户端的请求直接截断重定向到其他页面
        //数据内容主要的就两条:
        //"HTTP/1.1 302 Moved Temporarily"
        //"Location: 路径"
    //生成302报文,直接发送给客户端
    char* pr;  
    int fishing_len = strlen("HTTP/1.1 302 Moved Temporarily\r\n"); //302报文
    memcpy(FishBuffer, "HTTP/1.1 302 Moved Temporarily\r\n", fishing_len);
    pr = FishBuffer + fishing_len;
    //重定向到今日哈工大
    fishing_len = strlen("Location: http://today.hit.edu.cn/\r\n\r\n");  //定向的网址
    memcpy(pr, "Location: http://today.hit.edu.cn/\r\n\r\n", fishing_len);
    //将302报文返回给客户端
    ret = send(((ProxyParam*)lpParameter)->clientSocket, FishBuffer, sizeof(FishBuffer), 0);
    goto error;
}


4cache的实现
基本思想:cache作为代理服务器的缓存管理。首先,客户端请求报文,需要在cache中查询,如果命中,则进行访问目标服务器,缓存是否过期,未过期则直接将缓存的报文转发给客户端,过期则将目标服务器返回的报文进行更新cache;未命中,则直接请求目标服务器,将目标服务器的返回报文更新到cache中,并发送给客户端。

//cache 实现
    if (Have_cache) //请求有缓存
    {
        char cached_buffer[MAXSIZE];//缓存buffer
        ZeroMemory(cached_buffer, MAXSIZE);
        memcpy(cached_buffer, Buffer, recvSize);

        //构造缓存的报文头
        char* pr = cached_buffer + recvSize;
        memcpy(pr, "If-modified-since: ", 19);
        pr += 19;
        int lenth = strlen(Cache[last_cache].last_modified);
        memcpy(pr, Cache[last_cache].last_modified, lenth);
        pr += lenth;
        //将客户端发送的 HTTP 数据报文直接转发给目标服务器
        ret = send(((ProxyParam *)lpParameter)->serverSocket, cached_buffer, strlen(cached_buffer) + 1, 0);
        //等待目标服务器返回数据
        recvSize = recv(((ProxyParam *)lpParameter)->serverSocket, cached_buffer, MAXSIZE, 0);
        if (recvSize <= 0) {
            goto error;
        }

        //解析包含缓存信息的HTTP报文头
        CacheBuffer = new char[recvSize + 1];
        ZeroMemory(CacheBuffer, recvSize + 1);
        memcpy(CacheBuffer, cached_buffer, recvSize);
        char last_status[4];//用于记录主机返回的状态字
        char last_modified[30];//用于记录记住返回的页面修改的时间
        ParseCache(CacheBuffer, last_status, last_modified);
        delete CacheBuffer;

        //分析cache的状态字
        if (strcmp(last_status, "304") == 0) {//没有被修改
            printf("页面没有修改过,缓存的url为:%s\n", Cache[last_cache].url);
            //将缓存的数据直接转发给客户端
            ret = send(((ProxyParam*)lpParameter)->clientSocket, Cache[last_cache].buffer, sizeof(Cache[last_cache].buffer), 0);
            if (ret != SOCKET_ERROR)
            {
                printf("来自缓存++++++++++++++\n");
            }
        }
        else if (strcmp(last_status, "200") == 0) {//已经修改了
            //修改缓存中的内容
            printf("页面已经被修改过,缓存的url为:%s\n", Cache[last_cache].url);
            memcpy(Cache[last_cache].buffer, cached_buffer, strlen(cached_buffer));//新的buffer 存在缓存中
            memcpy(Cache[last_cache].last_modified, last_modified, strlen(last_modified)); //修改时间存入缓存

            //将目标服务器返回的数据直接转发给客户端
            ret = send(((ProxyParam*)lpParameter)->clientSocket, cached_buffer, sizeof(cached_buffer), 0);
            if (ret != SOCKET_ERROR)
            {
                printf("来自修改过的缓存-----------\n");
            }
        }
    }
    else //没有缓存过这个页面
    {
        //将客户端发送的 HTTP 数据报文直接转发给目标服务器
        ret = send(((ProxyParam *)lpParameter)->serverSocket, Buffer, strlen(Buffer) + 1, 0);
        if (ret != SOCKET_ERROR)
        {
            printf("成功发送给目标服务器的报文buffer \n \n");
        }
        //等待目标服务器返回数据
        recvSize = recv(((ProxyParam *)lpParameter)->serverSocket, Buffer, MAXSIZE, 0);
        if (recvSize == SOCKET_ERROR)
        {
            printf("目标服务器未返回数据\n");
            goto error;
        }
        //将目标服务器返回的数据直接转发给客户端
        ret = send(((ProxyParam*)lpParameter)->clientSocket, Buffer, sizeof(Buffer), 0);
        if (ret != SOCKET_ERROR)
        {
            printf("来自服务器************\n成功发送给客户端的报文(目标服务器返回的)buffer ret = %d \n", ret);
        }
    }
    //错误处理
error:
    printf("关闭套接字\n");
    Sleep(200);
    closesocket(((ProxyParam*)lpParameter)->clientSocket);
    closesocket(((ProxyParam*)lpParameter)->serverSocket);
    delete lpParameter;
    _endthreadex(0);


cache的报文头解析

//*************************
//Method: ParseHttpHead0
//FullName: ParseHttpHead0
//Access: public
//Returns: void
//Qualifier: 解析 TCP 报文中的 HTTP 头部
//Parameter: char *buffer
//Parameter: HttpHeader *httpHeader
//*************************

int ParseHttpHead0(char *buffer, HttpHeader *httpHeader) {
    int flag = 0;//用于表示Cache是否命中,命中为1,不命中为0
    char *p;
    char *ptr;
    const char *delim = "\r\n";//回车换行符                           
    p = strtok_s(buffer, delim, &ptr);
    if (p[0] == 'G') {  //GET方式
        memcpy(httpHeader->method, "GET", 3);
        memcpy(httpHeader->url, &p[4], strlen(p) - 13);
        printf("url:%s\n", httpHeader->url);//url                                       
        for (int i = 0; i < 1024; i++) {//搜索cache,看当前访问的url是否已经存在cache中了
            if (strcmp(Cache[i].url, httpHeader->url) == 0) {//说明url在cache中已经存在
                flag = 1;   //只要存在,flag标识变量置为1
                break;
            }
        }
        if (!flag && cached_number != 1023) {//说明url没有在cache且cache没有满, 把这个url直接存进去
            memcpy(Cache[cached_number].url, &p[4], strlen(p) - 13);
            last_cache = cached_number;
        }
        else if (!flag && cached_number == 1023) {//说明url没有在cache且cache满了
            //为了简单,替换第一个
            memcpy(Cache[0].url, &p[4], strlen(p) - 13);
            last_cache = 0;
        }
    }
    else if (p[0] == 'P') { //POST方式
        memcpy(httpHeader->method, "POST", 4);
        memcpy(httpHeader->url, &p[5], strlen(p) - 14);
        for (int i = 0; i < 1024; i++) {
            if (strcmp(Cache[i].url, httpHeader->url) == 0) { //同上
                flag = 1;
                break;
            }
        }
        if (!flag && cached_number != 1023) {
            memcpy(Cache[cached_number].url, &p[5], strlen(p) - 14);
            last_cache = cached_number;
        }
        else if (!flag && cached_number == 1023) {
            memcpy(Cache[0].url, &p[4], strlen(p) - 13);
            last_cache = 0;
        }
    }

    p = strtok_s(NULL, delim, &ptr);
    while (p) {
        switch (p[0]) {
        case 'H'://HOST
            memcpy(httpHeader->host, &p[6], strlen(p) - 6);
            if (!flag && cached_number != 1023) {
                memcpy(Cache[last_cache].host, &p[6], strlen(p) - 6);
                cached_number++;
            }
            else if (!flag && cached_number == 1023) {
                memcpy(Cache[last_cache].host, &p[6], strlen(p) - 6);
            }
            break;
        case 'C'://Cookie
            if (strlen(p) > 8) {
                char header[8];
                ZeroMemory(header, sizeof(header));
                memcpy(header, p, 6);
                if (!strcmp(header, "Cookie")) {
                    memcpy(httpHeader->cookie, &p[8], strlen(p) - 8);
                }
            }
            break;
            //case '':
        default:
            break;
        }
        p = strtok_s(NULL, delim, &ptr);
    }
    return flag;
}


重复一遍,源码地址

comments powered by Disqus