实验内容

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

代理服务器的概念

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

代理服务器在指定端口(本实验中所指定的是666端口)监听浏览器的访问请求(需要在客户端浏览器进行相应的设置),接收到浏览器对远程网站的浏览请求时,代理服务器开始在代理服务器的缓存中检索 URL 对应的对象(网页、 图像等对象),找到对象文件后,提取该对象文件的最新被修改时间;代理服务器程序在客户的请求报文首部插入,并向原 Web 服务器转发修改后的请求报文。如果代理服务器没有该对象的缓存,则会直接向原服务器转发请求报文,并将原服务器返回的响应直接转发给客户端,同时将对象缓存到代理服务器中。代理服务器程序会根据缓存的时间、大小和提取记录等对缓存进行清理。

代码结构

代码中共实现 3个类,分别为WebsiteDetector类、Cache类和HttpProxyServer类。

WebsiteDetector类:

该类实现了网站过滤和网站引导功能。通过构造函数直接静态设置了钓鱼网站和屏蔽的网站:

WebsiteDetector::WebsiteDetector()
{
AddValidURL("http://jwc.hit.edu.cn/","http://jwts.hit.edu.cn/");
AddBlockedURL("http://xltj.hit.edu.cn/");
}

可知,屏蔽了心理网站。将教务处网站引导到本科教学管理与服务平台。

Cache

该类在当前目录下创建文件夹.cache/,在其中存储浏览缓存对象。同时该类中,保存着对象与文件名的映射关系,对象和LastModified字段的映射关系。

class Cache {

public:

std::string GetDate(const std::string& url); // 获取url对应保存的LastModified字段

bool Get(const std::string& url, char* response, size_t& start, size_t& responseSize); // 读取缓存

bool Put(const std::string& url, const char* response, size_t responseSize, size_t& start); // 保存缓存

private:

​      std::string cacheDirectory_; // 存放缓存的文件目录

​      std::map<std::string, std::string> cacheMap_; // 对象和LastModified字段的映射关系

​      std::map<std::string, std::string> fileMap_; / 对象与文件名的映射关系

​      std::mutex mutex_; // 多线程同时读写文件的互斥锁

};

HttpProxyServer

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

class HttpProxyServer

{

public:

	HttpProxyServer(int port); // 构造函数,参数为端口号

	void Start(); // 监听客户端连接请求

private:

	int serverSocket_; // 代理服务Socket

	int port_; // 端口号

	struct sockaddr_in serverAddr_; // 代理服务地址

	Cache cache_; // Cache类

	WebsiteDetector websiteDetector_; // websiteDetector类

	void HandleClient(int clientSocket); // 子线程调用函数

	std::string ExtractUrl(const std::string &httpRequest); // 解析URL

	int CreateServerSocket(const std::string &host); // 创建与原服务器连接的Socket

	bool ServerToClient(const std::string &url, int clientSocket); // 转发数据

	void ParseUrl(const std::string &url, std::string &host, std::string &path); // 解析主机名与路径名

};

程序基本流程

(1) 初始化服务器Socket,监听等待客户端的连接请求。

(2) 当客户端连接后,创建子线程处理请求。

(3) 子线程接收请求,解析HTTP请求的首部行和请求头。然后提取Url,Url作为参数通过websiteDetector类判断是否屏蔽或者引导。

(4) 然后进入转发的过程,首先进行域名解析,然后创建Socket先原服务器发送请求,接收响应,将数据转发到客户端。

(5) 在转发的过程中,涉及保存缓存和读取缓存。

网站引导功

利用首部行中的location字段,实现引导。

std::string locationResponse = std::string("HTTP/1.1 302 Found") + MY_CRLF + "Location: " + newUrl + MY_CRLF + MY_CRLF;

send(clientSocket, locationResponse.c_str(), locationResponse.size(), 0);

用户过滤功能

设置服务器地址信息时实现。

serverAddr_.sin_addr.s_addr = INADDR_ANY;

// serverAddr_.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //只允许本机用户访问服务器

Cache功能

1. 代理服务器处理客户端请求时,对于第一次出现的对象,会保存下。当客户端再次请求时,代理服务器就会在请求中添加If-Modified-Since首部行。

date = cache_.GetDate(url);
std::string cacheRequest = httpRequest + "If-Modified-Since: " + date + MY_CRLF + MY_CRLF;
  1. 发送该请求后,等待原服务器响应,并判断是否回应304状态码。
if (IsResponseNotModified(responseNotModified) ) {
​ // std::cout << "304 Not Modified" << std::endl;
​ sel = false;
}else {
​ cache_.ClearFileContent(url); //清空
​ sel = true;
}
  1. sel为false时,则读取Cache转发到客户端。若为true,则发送HTTP请求到原服务器,再接收响应,转发到客户端,再保存到Cache。

修改Chrome浏览器代理配置

--proxy-server="http://127.0.0.1:666"

VScode编译运行

该代理服务器成功在666端口启动,并输出了cache目录。

验证

验证基础的代理功能

访问今日哈工大网站:http://today.hit.edu.cn

可以看到,网站资源顺利加载,输出栏中,输出了请求的各个资源对象的url。

验证网站引导功能

输入网址:http://jwc.hit.edu.cn/

最后直接跳转到到了,http://jwts.hit.edu.cn/

验证网站过滤功能

输入网址:http://xltj.hit.edu.cn/

可以看到,无法访问。

验证用户过滤功能

验证Cache功能

将在Cache中的资源 http://jwts.hit.edu.cn/resources/css/common/ydy.css ,修改一下。

把色彩均改为红色,再次访问 http://jwts.hit.edu.cn/

可以看到,字体颜色变为红色。可知,HTTP代理服务器这次使用的是Cache中的资源。

源代码

点击查看代码
//g++ your_code.cpp -o your_executable -lws2_32

#include <fstream>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <thread>
#include <vector>
#include <mutex>
#include <list>
#include <map>
#include <sstream>
#include <winsock2.h>
#include <WS2tcpip.h> #define MAX_CLIENTS 6
#define BUFSIZE 655360
#define HEADSIZE 128
#define MY_CRLF "\r\n" class WebsiteDetector {
public:
WebsiteDetector()
{
AddValidURL("http://jwc.hit.edu.cn/", "http://jwts.hit.edu.cn/");
AddBlockedURL("http://xltj.hit.edu.cn/");
}
// 钓鱼
std::string IsURLPhishing(const std::string& url) {
auto it = validURLs_.find(url);
if (it != validURLs_.end()) {
return it->second;
} else {
return "Phishing";
}
}
// 屏蔽
bool IsURLBlocked(const std::string& url) {
for (const std::string& blockedURL : blockedURLs_) {
if (url.find(blockedURL) != std::string::npos) {
return true;
}
}
return false;
}
private:
std::map<std::string, std::string> validURLs_;
std::vector<std::string> blockedURLs_; void AddValidURL(const std::string& srcURL, const std::string& dstURL)
{
validURLs_[srcURL] = dstURL;
} void AddBlockedURL(const std::string& url) {
blockedURLs_.push_back(url);
}
}; class Cache {
public: Cache() : cacheDirectory_("H:\\cppwork\\CS-networking\\.cache") {
std::cout << cacheDirectory_ << std::endl;
std::system(("mkdir -p " + cacheDirectory_).c_str());
}
bool Check(const std::string& url) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = cacheMap_.find(url);
if (it != cacheMap_.end())
{
return true;
}
return false;
}
// 清空文件内容
bool ClearFileContent(const std::string& url) {
std::lock_guard<std::mutex> lock(mutex_); // Generate a unique filename based on the URL
std::string fileName = GetFileNameFromUrl(url);
auto it = fileMap_.find(fileName);
std::string fileTag = it->second; std::string filePath = cacheDirectory_ + "\\" + fileTag;
// 打开文件并使用 std::ios::trunc 模式来清空文件内容
std::ofstream file(filePath, std::ios::out | std::ios::trunc);
if (!file) {
std::cerr << "无法打开文件:" << filePath << std::endl;
return false;
}
// 关闭文件
file.close();
return true;
}
std::string GetDate(const std::string& url)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = cacheMap_.find(url);
return it->second;
}
bool Get(const std::string& url, char* response, size_t& start, size_t& responseSize) {
std::lock_guard<std::mutex> lock(mutex_); // Generate a unique filename based on the URL
std::string fileName = GetFileNameFromUrl(url);
std::string fileTag = fileMap_[fileName]; std::cout << "Get() url: " << url << std::endl;
std::cout << "Get() fileTag: " << fileTag << std::endl;
// If found, read the response from the file
std::ifstream file(cacheDirectory_ + "\\" + fileTag, std::ios::binary);
if (file) {
file.seekg(start, std::ios::beg);
file.read(response, BUFSIZE);
// Get the number of bytes read in this chunk
size_t bytesRead = static_cast<size_t>(file.gcount());
start += bytesRead;
responseSize = bytesRead;
response[bytesRead] = '\0';
file.close();
return true;
}
return false; // URL not found in the cache
} bool Put(const std::string& url, const char* response, size_t responseSize, size_t& start) {
std::lock_guard<std::mutex> lock(mutex_); // Generate a unique filename based on the URL
std::string fileName = GetFileNameFromUrl(url);
auto it = fileMap_.find(fileName);
std::string fileTag;
if (it == fileMap_.end())
{
fileTag = std::to_string(cnt);
fileMap_[fileName] = fileTag;
cnt ++;
}else
{
fileTag = it->second;
} // Store the response in a file
std::ofstream file(cacheDirectory_ + "\\" + fileTag, std::ios::binary | std::ios::app );
if (!file) {
fprintf(stderr, "file open error: %s(errno: %d)\n", strerror(errno),errno);
return false; // Unable to open file for writing
}
file.seekp(start);
file.write(response, responseSize);
file.close();
start += responseSize;
return true; // Failed to store response in the cache
} void PutDate(const std::string& url)
{
std::lock_guard<std::mutex> lock(mutex_);
// Generate a unique filename based on the URL
std::string fileName = GetFileNameFromUrl(url);
std::string fileTag = fileMap_[fileName];
// 拼接完整的文件路径
std::string filePath = cacheDirectory_ + "\\" + fileTag; // 打开文件并读取 Last-Modified 首部内容
std::ifstream file(filePath);
if (!file) {
fprintf(stderr, "file open error: %s(errno: %d)\n", strerror(errno),errno);
}
std::string line;
while (std::getline(file, line)) {
// 查找包含 Last-Modified 首部的行
if (line.find("Last-Modified:") != std::string::npos) {
// 提取 Last-Modified 的值并存储到 cacheMap_
size_t startPos = line.find(":") + 2;
size_t endPos = line.find(MY_CRLF);
std::string date = line.substr(startPos, endPos);
// std::cout << "line: " << line << std::endl;
// std::cout << "date: " << date << std::endl;
cacheMap_[url] = date;
break; // 找到后可以退出循环
}else
{
if (line == MY_CRLF)
{
break;
}
}
}
file.close();
} private:
std::string cacheDirectory_;
std::map<std::string, std::string> cacheMap_;
std::map<std::string, std::string> fileMap_;
std::mutex mutex_;
int cnt = 1; std::string GetFileNameFromUrl(const std::string& url) {
// Replace characters in the URL to create a valid filename
std::string fileName = url;
for (char& c : fileName) {
if (c == '/' || c == '?' || c == '&' || c == '=') {
c = '_';
}
}
return fileName;
}
}; // 定义HTTP代理服务器类
class HttpProxyServer
{
public:
HttpProxyServer(int port) : port_(port)
{ // 初始化服务器
// 创建主套接字并绑定端口
serverSocket_ = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket_ == -1)
{
fprintf(stderr, "Constructor(): create socket error: %s(errno: %d)\n", strerror(errno),errno);
exit(EXIT_FAILURE);
} // 设置服务器地址信息
// 初始化 serverAddr_
memset(&serverAddr_, 0, sizeof(serverAddr_));
serverAddr_.sin_family = AF_INET;
serverAddr_.sin_addr.s_addr = INADDR_ANY;
// serverAddr_.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //只允许本机用户访问服务器
serverAddr_.sin_port = htons(port_); // 绑定套接字到指定端口
if (bind(serverSocket_, (struct sockaddr *)&serverAddr_, sizeof(serverAddr_)) == -1)
{
fprintf(stderr, "Constructor(): bind socket error: %s(errno: %d)\n",strerror(errno), errno);
closesocket(serverSocket_);
exit(EXIT_FAILURE);
} // 开始监听客户端连接请求
if (listen(serverSocket_, MAX_CLIENTS) == -1)
{
fprintf(stderr, "Constructor(): listen socket error: %s(errno: %d)\n",strerror(errno),errno);
closesocket(serverSocket_);
exit(EXIT_FAILURE);
} std::cout << "Proxy server started on port " << port_ << std::endl;
} void Start()
{
// 启动服务器,监听客户端连接请求
while (true)
{
struct sockaddr_in clientAddr;
int clientAddrLen = sizeof(struct sockaddr); // 接受客户端连接
int clientSocket = accept(serverSocket_, (struct sockaddr *)&clientAddr, &clientAddrLen);
if (clientSocket == INVALID_SOCKET)
{
fprintf(stderr, "Start(): accept socket error: %s(errno: %d)",strerror(errno),errno);
continue; // 继续等待下一个连接
} // std::cout << "Start(): Accepted a client connection" << std::endl; // 创建子线程处理客户端请求
std::thread clientThread(&HttpProxyServer::HandleClient, this, clientSocket);
clientThread.detach(); // 不等待
}
} private:
int serverSocket_;
int port_;
struct sockaddr_in serverAddr_;
Cache cache_;
WebsiteDetector websiteDetector_; void HandleClient(int clientSocket)
{
// 读取客户端的HTTP请求
char buffer[BUFSIZE];
memset(buffer, 0, BUFSIZE);
ssize_t bytesRead = recv(clientSocket, buffer, BUFSIZE - 1, 0);
if (bytesRead == -1)
{
perror("HandleClient(): Error reading from client socket");
closesocket(clientSocket);
return;
} // 解析请求,提取URL
std::string request(buffer);
std::string url = ExtractUrl(request); std::cout << "<" << url << ">" << std::endl; // Website Filter; User Filter ; Website phishing
if (websiteDetector_.IsURLBlocked(url))
{
std::cout << "Url Blocked Success: " << url << std::endl;
}else
{
std::string newUrl = websiteDetector_.IsURLPhishing(url);
if (newUrl == "Phishing")
{
// 向服务端请求,向客户端发送
if( ServerToClient(url, clientSocket) )
{
std::cout << "Transmit Success!" << std::endl;
}else
{
std::cout << "Transmit Fail!" << std::endl;
}
}else
{
std::cout << "Phishing" << std::endl;
std::string locationResponse = std::string("HTTP/1.1 302 Found") + MY_CRLF + "Location: " + newUrl + MY_CRLF + MY_CRLF;
std::cout << locationResponse << std::endl;
send(clientSocket, locationResponse.c_str(), locationResponse.size(), 0);
} } std::cout << "----------------------" << std::endl;
// 关闭连接
closesocket(clientSocket);
} // 提取URL
std::string ExtractUrl(const std::string &httpRequest)
{
std::string url;
// Debug
// std::cout << "ExtractUrl(): httpRequest = " << std::endl << httpRequest << std::endl; // 在HTTP请求中查找"GET ",通常URL紧随其后
size_t getPos = httpRequest.find("GET ");
if (getPos != std::string::npos)
{
// 找到"GET "后,查找下一个空格,该空格之后是URL
size_t spacePos = httpRequest.find(' ', getPos + 4);
if (spacePos != std::string::npos)
{
url = httpRequest.substr(getPos + 4, spacePos - (getPos + 4));
}
}
return url;
} void ParseUrl(const std::string &url, std::string &host, std::string &path)
{
// 查找 URL 中的 "http://",并获取其后的部分
size_t httpPos = url.find("http://");
if (httpPos != std::string::npos)
{
std::string urlWithoutHttp = url.substr(httpPos + 7); // 7 是 "http://" 的长度
// 查找 "/",分隔主机名和路径
size_t slashPos = urlWithoutHttp.find('/');
if (slashPos != std::string::npos)
{
host = urlWithoutHttp.substr(0, slashPos);
path = urlWithoutHttp.substr(slashPos);
}
else
{
// 如果没有找到 "/",则整个剩余部分都是主机名
host = urlWithoutHttp;
path = "/";
}
}
else
{
// 如果没有 "http://" 前缀,则默认协议为 HTTP,整个 URL 都是主机名
host = url;
path = "/";
} // Debug
// std::cout << "url: " + url << std::endl;
// std::cout << "host: " + host << std::endl;
// std::cout << "path: " + path << std::endl; }
int CreateServerSocket(const std::string &host)
{
// 域名解析
addrinfo* result = NULL;
addrinfo hints; ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; // 使用IPv4地址
hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(host.c_str(), "http", &hints, &result) != 0)
{
fprintf(stderr, "CreateServerSocket(): Failed to resolve the host: %s\n", host.c_str());
return -1; // 返回-1表示连接失败
} // 创建Socket
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == -1)
{
fprintf(stderr, "CreateServerSocket(): create socket error: %s(errno: %d)\n", strerror(errno), errno);
freeaddrinfo(result); // 释放内存
return -1; // 返回-1表示连接失败
} // 设置服务器地址信息
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(80); // 设置端口号为80,可以根据需要修改
serverAddr.sin_addr.s_addr = ((struct sockaddr_in *)(result->ai_addr))->sin_addr.s_addr; // 连接到原服务器
if (connect(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1)
{
fprintf(stderr, "CreateServerSocket(): connect error: %s(errno: %d)\n",strerror(errno),errno);
closesocket(serverSocket); // 在Windows中使用closesocket关闭套接字
freeaddrinfo(result); // 释放内存
return -1; // 返回-1表示连接失败
}
freeaddrinfo(result); // 释放内存
return serverSocket; // 返回连接成功的套接字描述符
} bool ServerToClient(const std::string &url, int clientSocket)
{
// 解析URL,获取主机名和路径
std::string host, path;
ParseUrl(url, host, path); // 创建Socket连接到原服务器
int serverSocket = CreateServerSocket(host);
if (serverSocket == -1)
{
return FALSE; // 处理连接失败的情况
}
// 构建HTTP请求
std::string httpRequest = "GET " + path + " HTTP/1.1" + MY_CRLF + "Host: " + host + MY_CRLF + "Connection: close" + MY_CRLF; std::string date;
bool sel; if (cache_.Check(url))
{
sel = false;
date = cache_.GetDate(url);
std::string cacheRequest = httpRequest + "If-Modified-Since: " + date + MY_CRLF + MY_CRLF;
// 发送HTTP, 带有If-Modified-Since 首部行
if (send(serverSocket, cacheRequest.c_str(), cacheRequest.size(), 0) == -1)
{
perror("Error sending request to server");
closesocket(serverSocket);
return FALSE;
}
std::string cacheResponse;
char cacheBuffer[HEADSIZE];
ssize_t cacheBytesRead;
cacheBytesRead = recv(serverSocket, cacheBuffer, HEADSIZE - 1, 0);
std::string responseNotModified(cacheBuffer); // std::cout << "responseNotModified: " << responseNotModified << std::endl; if (IsResponseNotModified(responseNotModified) )
{
// std::cout << "304 Not Modified" << std::endl;
sel = false;
}else
{
cache_.ClearFileContent(url); //清空
sel = true;
} }else
{
sel = true;
} if (sel == false)
{ // std::cout << "cache hit!" << std::endl;
// 接收缓存,转发到客户端
char buffer[BUFSIZE];
size_t start = 0;
size_t bytesRead;
while (1)
{
if (cache_.Get(url, buffer, start, bytesRead) == false)
{
perror("Error sending response to client");
} // std::cout << "bytesRead: " << bytesRead << std::endl;
if (bytesRead == 0) break; if (send(clientSocket, buffer, bytesRead, 0) == -1)
{
perror("Error sending response to client");
closesocket(serverSocket);
return FALSE;
}
} }else
{
httpRequest += MY_CRLF;
// 发送HTTP请求到原服务器
if (send(serverSocket, httpRequest.c_str(), httpRequest.size(), 0) == -1)
{
perror("Error sending request to server");
closesocket(serverSocket);
return FALSE;
} // 接收原服务器的HTTP响应
char buffer[BUFSIZE];
size_t start = 0;
ssize_t bytesRead;
while ((bytesRead = recv(serverSocket, buffer, BUFSIZE - 1, 0)) > 0)
{
buffer[bytesRead] = '\0';
// 发送接收到的数据到客户端
if (send(clientSocket, buffer, bytesRead, 0) == -1)
{
perror("Error sending response to client");
closesocket(serverSocket);
return FALSE;
}
if(cache_.Put(url, buffer, bytesRead, start) == false)
{
std::cerr << "Cache put error" << std::endl;
}
}
cache_.PutDate(url);
if (! cache_.Check(url))
{
cache_.ClearFileContent(url);
}
}
// 关闭原服务器连接
closesocket(serverSocket);
return TRUE;
}
bool IsResponseNotModified(const std::string& response) {
// 查找第一个空格,定位到状态码的开始
size_t spacePos = response.find(' ');
if (spacePos != std::string::npos) {
// 提取状态码部分
std::string statusCode = response.substr(spacePos + 1, 3);
// 检查状态码是否为 "304"
return (statusCode == "304"); // HTTP/1.1 304 Not Modified
}
return false; // 未找到状态码
}
}; bool InitWinsock()
{
// 加载套接字库(必须)
WORD wVersionRequested;
WSADATA wsaData;
// 套接字加载时错误提示
int err;
// 版本 2.2
wVersionRequested = MAKEWORD(2, 2);
// 加载 dll 文件 Scoket 库
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
// 找不到 winsock.dll
printf("加载 winsock 失败,错误代码为: %d\n", WSAGetLastError());
return FALSE;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("不能找到正确的 winsock 版本\n");
return FALSE;
} return TRUE;
} int main()
{
if (!InitWinsock())
{
WSACleanup();
return -1; // 初始化失败,退出程序
}
int port = 666; // 设置端口
HttpProxyServer proxyServer(port);
proxyServer.Start();
WSACleanup(); // 在程序结束时清理Winsock库
return 0;
}

HTTP 代理服务器的设计与实现(C++)的更多相关文章

  1. 哈工大 计算机网络 实验一 HTTP 代理服务器的设计与实现

    计算机网络实验代码与文件可见github:计算机网络实验整理 实验名称 HTTP 代理服务器的设计与实现 实验目的: 熟悉并掌握 Socket 网络编程的过程与技术:深入理解 HTTP 协议, 掌握 ...

  2. Flask 教程 第二十三章:应用程序编程接口(API)

    本文翻译自The Flask Mega-Tutorial Part XXIII: Application Programming Interfaces (APIs) 我为此应用程序构建的所有功能都只适 ...

  3. 优酷、YouTube、Twitter及JustinTV视频网站架构设计笔记

    本文是整理的关于优酷.YouTube.Twitter及JustinTV几个视频网站的架构或笔记,对于不管是视频网站.门户网站或者其它的网站,在架构上都有一定的参考意义,毕竟成功者的背后总有值得学习的地 ...

  4. 如何设计一个RPC系统

    版权声明:本文由韩伟原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/162 来源:腾云阁 https://www.qclou ...

  5. 代理服务器基本知识普及代理IP使用方法!

    本文并未从专业角度进行详细讲解,而是从应用的角度出发来普及一些代理服务器的基本知识.文章明显是搜集多方资料的拼凑,而且比较老了,但往往越老的东西越接近事物的本质,更容易窥探到原理,对于刚接触的人来说, ...

  6. 【翻译】使用nginx作为反向代理服务器,uWSGI作为应用服务器来部署flask应用

    最近在看关于Docker和Nginx方面的内容,先于在Docker上开发以及部署python应用自然要先能够在本机上部署,其中找到一篇文章写的最为详细并且实验成功,所以在此翻译转载过来以备后需.[原文 ...

  7. 借助nginx搭建反向代理服务器小例

    1 反向代理: 反向代理(Reverse Proxy)方式是指以代理服务器接收internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接 ...

  8. 微服务从设计到部署(二)使用 API 网关

    链接:https://github.com/oopsguy/microservices-from-design-to-deployment-chinese 译者:Oopsguy 本书的七个章节是关于设 ...

  9. 推荐一个比FiddlerCore好用的HTTP(S)代理服务器

    为什么不用FiddlerCore? 说到FiddlerCore大家可能会比较陌生,那么它哥Fiddler就比较熟悉了:抓包.模拟低带宽.修改请求我平时比较常用.Fiddler的本质就是一个HTTP代理 ...

  10. 开发一个http代理服务器

    参考链接: http://www.cnblogs.com/jivi/archive/2013/03/10/2952860.html https://www.2cto.com/kf/201405/297 ...

随机推荐

  1. PLSQL_developer安装与配置

    前言: 记录安装与配置操作 环境: 客户机:windows 服务器:虚拟机中的windows server 2003 /---------------------------------------- ...

  2. Django框架项目之登录注册——1-登录注册页面、2 多方式登录、3-手机是否存在验证接口、4-腾讯云短信开发、5 短信验证码接口、6-短信登录接口、7-短信注册接口、8-前台登录注册修订

    文章目录 1-登录注册页面 模态登录组件 模态注册组件 导航条:结合实际情况完成样式 登录业务分析 多方式登录 验证码登录 注册业务分析 验证码注册 汇总 2 多方式登录 后台 插件 urls.py ...

  3. 前端三件套系例之HTML——HTML5基础

    1.HTML 1-1 什么是HTML HTML是用来制作网页的标记语言 HTML是Hypertext Markup Language的英文缩写,即超文本标记语言 HTML语言是一种标记语言,不需要编译 ...

  4. linux日常运维(三) GRUB 2的维护

    GRUB 2简介 GRUB GRUB是linux系统默认的引导加载程序.linux加载一个系统前,它必须有一个引导加载程序中特定指令(比如MBR记录)去引导系统.这个程序一般是位于系统的主硬盘驱动器或 ...

  5. Python如何在日志中隐藏明文密码

    Python如何在日志中隐藏明文密码 前言 在项目开发中,有的时候会遇到一些安全需求,用以提升程序整体的安全性,提高外来非法攻击的门槛,而在日志中隐藏明文密码打印便是最典型的安全需求之一. 在Pyth ...

  6. 【实操】Java+百度ocr,实现图片识别文字小工具

    前言 缘由 图片识别文字,咱用java也可以 通过java+百度ocr,实现一个截图或上传图片,图片识别文字的小工具.并通过exe4j工具将jar包封装成exe可执行桌面文件,方便使用及学习. Tip ...

  7. React项目中webpack的配置过程

    初始化一个web项目 使用npm init -y 初始化一个项目 在项目目录下创建src, dist文件夹,创建webpack.config.js配置文件 然后在src文件夹下创建index.js, ...

  8. Go 包操作之如何拉取私有的Go Module

    Go 包操作之如何拉取私有的Go Module 在前面,我们已经了解了GO 项目依赖包管理与Go Module常规操作,Go Module 构建模式已经成为了 Go 语言的依赖管理与构建的标准. 在平 ...

  9. APIO 2023 游记

    真心话大冒险很有趣. rand 一个房间去敲门加 QQ 很有趣.这么看社恐猫好像也没那么社恐. 面到了 zpl pcq iee dx.单方面认识了很多神仙. 比赛只会写暴力,评测 queue 害人不浅 ...

  10. 动态规划——提高Ⅴ(DP优化)

    单调队列优化DP 其实单调队列就是一种队列内的元素有单调性(单调递增或者单调递减)的队列,答案(也就是最优解)就存在队首,而队尾则是最后进队的元素.因为其单调性所以经常会被用来维护区间最值或者降低DP ...