关于 HTTP GET/POST 请求参数长度最大值的问题

关于 HTTP GET/POST 请求参数长度最大值的一个理解误区 2014-09-24 14:02:11

分类: Java

转自:http://blog.chinaunix.net/uid-26602509-id-4495786.html

1.    Get方法长度限制

Http Get方法提交的数据大小长度并没有限制,HTTP协议规范没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。

如:IE对URL长度的限制是2083字节(2K+35)。

下面就是对各种浏览器和服务器的最大处理能力做一些说明.

Microsoft Internet Explorer (Browser)

IE浏览器对URL的最大限制为2083个字符,如果超过这个数字,提交按钮没有任何反应。
Firefox (Browser)

对于Firefox浏览器URL的长度限制为65,536个字符。

Safari (Browser)

URL最大长度限制为 80,000个字符。

Opera (Browser)

URL最大长度限制为190,000个字符。

Google (chrome)

URL最大长度限制为8182个字符。

Apache (Server)

能接受最大url长度为8,192个字符。

Microsoft Internet Information Server(IIS)

能接受最大url的长度为16,384个字符。

通过上面的数据可知,为了让所有的用户都能正常浏览, URL最好不要超过IE的最大长度限制(2083个字符),当然,如果URL不直接提供给用户,而是提供给程序调用,这时的长度就只受Web服务器影响了。

注:对于中文的传递,最终会为urlencode后的编码形式进行传递,如果浏览器的编码为UTF8的话,一个汉字最终编码后的字符长度为9个字符。

因此如果使用的 GET 方法,最大长度等于URL最大长度减去实际路径中的字符数。

2.    POST方法长度限制

理论上讲,POST是没有大小限制的。HTTP协议规范也没有进行大小限制,起限制作用的是服务器的处理程序的处理能力。

如:在Tomcat下取消POST大小的限制(Tomcat默认2M);

打开tomcat目录下的conf目录,打开server.xml 文件,修改

debug="0"

acceptCount="100"

connectionTimeout="20000"

disableUploadTimeout="true"

port="8080"

redirectPort="8443"

enableLookups="false"

minSpareThreads="25"

maxSpareThreads="75"

maxThreads="150"

maxPostSize="0"

URIEncoding="GBK"

增加红色字体部分 maxPostSize="0" (设为0是取消POST的大小限制)





刚看到群里又有同学在说 HTTP 协议下的 Get 请求参数长度是有大小限制的,最大不能超过

 XX,而 Post 是无限制的,看到这里,我想他们定是看多了一些以讹传讹的博客或者书籍,

导致一种理解上的误区:

1、首先即使有长度限制,也是限制的是整个 URI 长度,而不仅仅是你的参数值数据长度。

2、HTTP 协议从未规定 GET/POST 的请求长度限制是多少。

The HTTP protocol does not place any a priori limit on the length of a URI. Servers MUST be able to handle the URI of any resource they serve, and SHOULD be able to handle URIs of unbounded length if they provide GET-based forms that could generate such URIs. A server SHOULD return 414 (Request-URI Too Long) status if a URI is longer than the server can handle (see section 10.4.15). 
 Note: Servers ought to be cautious about depending on URI lengths above 255 bytes, because some older client or proxy implementations might not properly support these lengths.

3、所谓的请求长度限制是由浏览器和 web 服务器决定和设置的,各种浏览器和 web 服务器的设定

均不一样,这依赖于各个浏览器厂家的规定或者可以根据 web 服务器的处理能力来设定。

The limit is in MSIE and Safari about 2KB, in Opera about 4KB and in Firefox about 8KB, (255 bytes if we count very old browsers)  . We may thus assume that 8KB is the maximum possible length and that 2KB is a more affordable length to rely on at the server side and that 255 bytes is the safest length to assume that the entire URL will come in.
If the limit is exceeded in either the browser or the server, most will just truncate the characters outside the limit without any warning. Some servers however may send a HTTP 414 error. If you need to send large data, then better use POST instead of GET. Its limit is much higher, but more dependent on the server used than the client. Usually up to around 2GB is allowed by the average webserver. This is also configureable somewhere in the server settings. The average server will display a server-specific error/exception when the POST limit is exceeded, usually as HTTP 500 error.
HTTP 1.1 defines Status Code 414 Request-URI Too Long for the cases where a server-defined limit is reached. You can see further details on RFC 2616. For the case of client-defined limits, there is no sense on the server returning something, because the server won't receive the request at all.
The server is refusing to service the request because the Request-URI is longer than the server is willing to interpret. This rare condition is only likely to occur when a client has improperly converted a POST request to a GET request with long query information, when the client has descended into a URI "black hole" of redirection (e.g., a redirected URI prefix that points to a suffix of itself), or when the server is under attack by a client attempting to exploit security holes present in some servers using fixed-length buffers for reading or manipulating the Request-URI.


附 GET VS POST:

1、多数浏览器对于POST采用两阶段发送数据的,先发送请求头,再发送请求体,即使参数再少再短,也会被分成两个步骤来发送(相对于GET),也就是第一步发送header数据,第二步再发送body部分。HTTP是应用层的协议,而在传输层有些情况TCP会出现两次连结的过程,HTTP协议本身不保存状态信息,一次请求一次响应。对于TCP而言,通信次数越多反而靠性越低,能在一次连结中传输完需要的消息是最可靠的,尽量使用GET请求来减少网络耗时。如果通信时间增加,这段时间客户端与服务器端一直保持连接状态,在服务器侧负载可能会增加,可靠性会下降。

Tips:关于这点你可以参考:Yahoo网站性能优化指南之服务器篇

http://segmentfault.com/a/1190000000353790


http://developer.yahoo.com/performance/rules.html

http://blogread.cn/it/article/6100?f=wb    YSLOW法则中,为什么yahoo推荐用GET代替POST?

上面这篇文章介绍了 wireshark 抓包验证 post 两次发包,get 一次发包的全过程,推荐阅读。



2、GET请求能够被cache,GET请求能够被保存在浏览器的浏览历史里面(密码等重要数据GET提交,别人查看历史记录,就可以直接看到这些私密数据)POST不进行缓存。

3、GET参数是带在URL后面,传统IE中URL的最大可用长度为2048字符,其他浏览器对URL长度限制实现上有所不同。POST请求无长度限制(目前理论上是这样的)。

4、GET提交的数据大小,不同浏览器的限制不同,一般在2k-8K之间,POST提交数据比较大,大小靠服务器的设定值限制,而且某些数据只能用 POST 方法「携带」,比如 file。

5、全部用POST不是十分合理,最好先把请求按功能和场景分下类,对数据请求频繁,数据不敏感且数据量在普通浏览器最小限定的2k范围内,这样的情况使用GET。其他地方使用POST。

6、GET 的本质是「得」,而 POST 的本质是「给」。而且,GET 是「幂等」的,在这一点上,GET 被认为是「安全的」。但实际上 server 端也可以用作资源更新,但是这种用法违反了约定,容易造成 CSRF(跨站请求伪造)。


REF:

maximum length of HTTP GET request?

http://stackoverflow.com/questions/2659952/maximum-length-of-http-get-request  

http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.15 Request-URI Too Long

http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2.1 General Syntax

http://www.cnblogs.com/xiaotaomaomao/articles/986070.html

http://www.cnblogs.com/TankXiao/archive/2012/02/13/2342672.html HTTP协议详解

post方式相比get安全,携带数据更大,我准备所有数据都用post方式获取,这样好吗?

http://segmentfault.com/q/1010000000213082  

http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html   浅谈CSRF攻击方式

<think>我们正在实现一个支持GETPOST请求的多线程Web服务器,使用C语言编写,并添加详细注释。我们将使用线程池和epoll来实现高并发处理。以下是一个整合的代码,包含详细注释: 设计要点: 1. 使用线程池处理连接,避免频繁创建销毁线程。 2. 使用epoll进行I/O多路复用,监听多个套接字。 3. 解析HTTP请求,支持GETPOST方法。 4. 实现简单的路由,处理不同的请求路径。 5. 发送HTTP响应,包括状态码、头部和内容。 注意:为了简化,我们不考虑持久连接(Keep-Alive),每次请求后关闭连接。 我们将代码写在一个文件中,并添加详细的注释。</think>以下是一个详细注释的C语言多线程Web服务器实现,支持GET/POST请求处理和高并发性能。代码整合在单个C文件中: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <pthread.h> #include <sys/epoll.h> #include <fcntl.h> #include <sys/sendfile.h> #include <sys/stat.h> #define PORT 8080 // 服务器监听端口 #define THREAD_POOL_SIZE 16 // 线程池大小 #define MAX_EVENTS 1024 // epoll最大事件数 #define BUFFER_SIZE 8192 // 读写缓冲区大小 #define BACKLOG 128 // 最大等待连接数 #define MAX_PATH_LEN 256 // 最大路径长度 // HTTP请求结构体 typedef struct { char method[16]; // 请求方法(GET/POST) char path[MAX_PATH_LEN]; // 请求路径 char protocol[16]; // 协议版本 char headers[BUFFER_SIZE]; // 请求头 char *body; // 请求体(POST使用) int content_length; // 内容长度 } HttpRequest; // HTTP响应结构体 typedef struct { int status_code; // 状态码 char *content; // 响应内容 size_t content_len; // 内容长度 char headers[BUFFER_SIZE]; // 响应头 } HttpResponse; // 任务队列节点 typedef struct TaskNode { int client_fd; // 客户端文件描述符 struct TaskNode *next; // 下一个任务指针 } TaskNode; // 线程安全任务队列 typedef struct { TaskNode *front; // 队列头部 TaskNode *rear; // 队列尾部 pthread_mutex_t lock; // 互斥锁 pthread_cond_t cond; // 条件变量 int shutdown; // 关闭标志 } TaskQueue; // 线程池结构 typedef struct { pthread_t threads[THREAD_POOL_SIZE]; // 工作线程数组 TaskQueue *queue; // 任务队列 int shutdown; // 关闭标志 } ThreadPool; // 初始化任务队列 TaskQueue* init_task_queue() { TaskQueue *q = malloc(sizeof(TaskQueue)); if (!q) { perror("Failed to allocate task queue"); exit(EXIT_FAILURE); } q->front = q->rear = NULL; q->shutdown = 0; // 初始化互斥锁和条件变量 if (pthread_mutex_init(&q->lock, NULL) != 0) { perror("Mutex init failed"); exit(EXIT_FAILURE); } if (pthread_cond_init(&q->cond, NULL) != 0) { perror("Condition variable init failed"); exit(EXIT_FAILURE); } return q; } // 添加任务到队列 void enqueue(TaskQueue *q, int client_fd) { // 创建新任务节点 TaskNode *node = malloc(sizeof(TaskNode)); if (!node) { perror("Failed to allocate task node"); return; } node->client_fd = client_fd; node->next = NULL; // 加锁操作队列 pthread_mutex_lock(&q->lock); // 队列为空时直接添加 if (q->rear == NULL) { q->front = q->rear = node; } // 队列非空时添加到尾部 else { q->rear->next = node; q->rear = node; } // 唤醒等待线程并解锁 pthread_cond_signal(&q->cond); pthread_mutex_unlock(&q->lock); } // 从队列获取任务 int dequeue(TaskQueue *q) { pthread_mutex_lock(&q->lock); // 队列为空时等待条件变量 while (q->front == NULL && !q->shutdown) { pthread_cond_wait(&q->cond, &q->lock); } // 服务器关闭时返回-1 if (q->shutdown) { pthread_mutex_unlock(&q->lock); return -1; } // 获取队列头部任务 TaskNode *node = q->front; int client_fd = node->client_fd; // 更新队列头部 if (q->front == q->rear) { q->front = q->rear = NULL; } else { q->front = q->front->next; } // 释放节点内存并解锁 free(node); pthread_mutex_unlock(&q->lock); return client_fd; } // 工作线程函数 void* worker_thread(void *arg) { ThreadPool *pool = (ThreadPool*)arg; // 持续处理任务直到关闭 while (!pool->shutdown) { // 从队列获取客户端连接 int client_fd = dequeue(pool->queue); if (client_fd < 0) continue; // 处理客户端请求 handle_client(client_fd); // 关闭客户端连接 close(client_fd); } return NULL; } // 初始化线程池 ThreadPool* init_thread_pool(TaskQueue *queue) { ThreadPool *pool = malloc(sizeof(ThreadPool)); if (!pool) { perror("Failed to allocate thread pool"); exit(EXIT_FAILURE); } pool->queue = queue; pool->shutdown = 0; // 创建工作线程 for (int i = 0; i < THREAD_POOL_SIZE; i++) { if (pthread_create(&pool->threads[i], NULL, worker_thread, pool) != 0) { perror("Failed to create thread"); exit(EXIT_FAILURE); } } return pool; } // 创建服务器套接字 int create_server_socket(int port) { int server_fd; struct sockaddr_in addr; // 创建TCP套接字 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket creation failed"); exit(EXIT_FAILURE); } // 设置SO_REUSEADDR选项避免TIME_WAIT状态 int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) { perror("Setsockopt failed"); close(server_fd); exit(EXIT_FAILURE); } // 配置服务器地址 addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(port); // 绑定地址到套接字 if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("Bind failed"); close(server_fd); exit(EXIT_FAILURE); } // 开始监听连接 if (listen(server_fd, BACKLOG) < 0) { perror("Listen failed"); close(server_fd); exit(EXIT_FAILURE); } return server_fd; } // 解析HTTP请求 HttpRequest* parse_request(char *buffer) { HttpRequest *req = malloc(sizeof(HttpRequest)); if (!req) { perror("Failed to allocate request"); return NULL; } memset(req, 0, sizeof(HttpRequest)); // 解析请求行: METHOD PATH PROTOCOL if (sscanf(buffer, "%15s %255s %15s", req->method, req->path, req->protocol) != 3) { free(req); return NULL; } // 查找头部结束位置(空行) char *header_end = strstr(buffer, "\r\n\r\n"); if (header_end) { // 计算头部长度并复制 size_t header_len = header_end - buffer; strncpy(req->headers, buffer, header_len); req->headers[header_len] = '\0'; // 处理POST请求体 if (strcmp(req->method, "POST") == 0) { // 查找Content-Length头部 char *cl_header = strstr(req->headers, "Content-Length:"); if (cl_header) { // 获取内容长度 if (sscanf(cl_header, "Content-Length: %d", &req->content_length) != 1) { req->content_length = 0; } // 分配内存并复制请求体 if (req->content_length > 0) { req->body = malloc(req->content_length + 1); if (!req->body) { perror("Failed to allocate request body"); free(req); return NULL; } memcpy(req->body, header_end + 4, req->content_length); req->body[req->content_length] = '\0'; } } } } return req; } // 发送HTTP响应 void send_response(int client_fd, HttpResponse *res) { char status_line[128]; // 根据状态码生成状态行 switch (res->status_code) { case 200: sprintf(status_line, "HTTP/1.1 200 OK\r\n"); break; case 404: sprintf(status_line, "HTTP/1.1 404 Not Found\r\n"); break; case 500: sprintf(status_line, "HTTP/1.1 500 Internal Server Error\r\n"); break; default: sprintf(status_line, "HTTP/1.1 %d Status\r\n", res->status_code); } // 构建完整响应 char full_response[BUFFER_SIZE * 2]; int header_len = snprintf(full_response, sizeof(full_response), "%s%sContent-Length: %zu\r\n\r\n", status_line, res->headers, res->content_len); // 复制响应内容 if (res->content && res->content_len > 0) { memcpy(full_response + header_len, res->content, res->content_len); } // 计算总长度并发送 size_t total_len = header_len + res->content_len; send(client_fd, full_response, total_len, 0); } // 安全路径检查(防止路径遍历攻击) int is_safe_path(const char *path) { // 检查是否包含父目录引用 if (strstr(path, "..") != NULL) { return 0; } // 检查是否尝试访问根目录之外 if (strstr(path, "/etc/passwd") != NULL) { return 0; } return 1; } // 处理GET请求 void handle_get(int client_fd, HttpRequest *req, HttpResponse *res) { // 默认响应 res->status_code = 200; res->content = "Hello, World!"; res->content_len = strlen(res->content); // 简单路由处理 if (strcmp(req->path, "/") == 0) { // 首页响应 res->content = "<html><body><h1>Welcome to C Web Server</h1></body></html>"; res->content_len = strlen(res->content); snprintf(res->headers, sizeof(res->headers), "Content-Type: text/html\r\n"); } // 处理文件请求 else { char file_path[MAX_PATH_LEN]; snprintf(file_path, sizeof(file_path), ".%s", req->path); // 检查路径安全性 if (!is_safe_path(file_path)) { res->status_code = 403; res->content = "Forbidden"; res->content_len = strlen(res->content); return; } // 检查文件是否存在 struct stat st; if (stat(file_path, &st) == 0) { // 打开文件 int fd = open(file_path, O_RDONLY); if (fd >= 0) { // 设置响应头 const char *ext = strrchr(file_path, '.'); if (ext) { if (strcmp(ext, ".html") == 0) { snprintf(res->headers, sizeof(res->headers), "Content-Type: text/html\r\n"); } else if (strcmp(ext, ".css") == 0) { snprintf(res->headers, sizeof(res->headers), "Content-Type: text/css\r\n"); } else if (strcmp(ext, ".js") == 0) { snprintf(res->headers, sizeof(res->headers), "Content-Type: application/javascript\r\n"); } else { snprintf(res->headers, sizeof(res->headers), "Content-Type: application/octet-stream\r\n"); } } // 使用sendfile零拷贝传输文件 sendfile(client_fd, fd, NULL, st.st_size); close(fd); return; } } // 文件不存在 res->status_code = 404; res->content = "404 Not Found"; res->content_len = strlen(res->content); } } // 处理POST请求 void handle_post(HttpRequest *req, HttpResponse *res) { // 默认响应 res->status_code = 200; res->content = "Form submitted successfully"; res->content_len = strlen(res->content); // 示例:处理表单提交 if (strstr(req->path, "/submit")) { printf("Received POST data: %.*s\n", req->content_length, req->body); snprintf(res->headers, sizeof(res->headers), "Content-Type: text/plain\r\n"); } // 处理文件上传 else if (strstr(req->path, "/upload")) { // 查找boundary char *boundary = strstr(req->headers, "boundary="); if (boundary) { boundary += 9; // 移动到boundary值 // 截断boundary字符串 char *end = strchr(boundary, '\r'); if (end) *end = '\0'; // 解析multipart数据 char *body_ptr = req->body; while ((body_ptr = strstr(body_ptr, boundary))) { // 跳过头部信息 body_ptr = strstr(body_ptr, "\r\n\r\n"); if (!body_ptr) break; body_ptr += 4; // 跳过空行 // 查找下一个boundary char *next_boundary = strstr(body_ptr, boundary); if (!next_boundary) break; // 计算文件内容长度 size_t file_size = next_boundary - body_ptr - 4; // 减去前置的\r\n // 写入文件 FILE *file = fopen("uploaded_file", "wb"); if (file) { fwrite(body_ptr, 1, file_size, file); fclose(file); res->content = "File uploaded successfully"; res->content_len = strlen(res->content); return; } } } // 文件上传失败 res->status_code = 500; res->content = "File upload failed"; res->content_len = strlen(res->content); } } // 处理客户端请求 void handle_client(int client_fd) { char buffer[BUFFER_SIZE]; // 读取客户端请求 ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0); // 错误或连接关闭 if (bytes_read <= 0) { close(client_fd); return; } // 确保字符串以NULL结尾 buffer[bytes_read] = '\0'; // 解析HTTP请求 HttpRequest *req = parse_request(buffer); if (!req) { close(client_fd); return; } // 初始化响应结构 HttpResponse res = {0}; // 根据请求方法路由处理 if (strcmp(req->method, "GET") == 0) { handle_get(client_fd, req, &res); } else if (strcmp(req->method, "POST") == 0) { handle_post(req, &res); } else { // 不支持的HTTP方法 res.content = "Method Not Allowed"; res.content_len = strlen(res.content); res.status_code = 405; } // 发送响应(文件请求已直接发送,跳过) if (res.content) { send_response(client_fd, &res); } // 清理资源 if (req->body) free(req->body); free(req); } int main() { // 创建服务器套接字 int server_fd = create_server_socket(PORT); printf("Server listening on port %d\n", PORT); // 创建任务队列 TaskQueue *queue = init_task_queue(); // 创建线程池 ThreadPool *pool = init_thread_pool(queue); // 创建epoll实例 int epoll_fd = epoll_create1(0); if (epoll_fd < 0) { perror("epoll_create1 failed"); exit(EXIT_FAILURE); } // 添加服务器套接字到epoll struct epoll_event ev; ev.events = EPOLLIN; // 监听可读事件 ev.data.fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) < 0) { perror("epoll_ctl failed"); exit(EXIT_FAILURE); } // 主事件循环 struct epoll_event events[MAX_EVENTS]; while (1) { // 等待事件(无限等待) int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds < 0) { perror("epoll_wait failed"); continue; } // 处理所有就绪事件 for (int i = 0; i < nfds; i++) { // 新连接请求 if (events[i].data.fd == server_fd) { struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); // 接受新连接 int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len); if (client_fd < 0) { perror("accept failed"); continue; } // 设置非阻塞模式 int flags = fcntl(client_fd, F_GETFL, 0); fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); // 添加新连接到epoll(边缘触发模式) ev.events = EPOLLIN | EPOLLET; ev.data.fd = client_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) < 0) { perror("epoll_ctl add client failed"); close(client_fd); } } // 现有连接有数据可读 else { // 添加到任务队列 enqueue(queue, events[i].data.fd); // 从epoll中移除,避免重复处理 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL); } } } // 清理资源(实际不会执行到这里) close(server_fd); close(epoll_fd); return 0; } ``` ### 关键设计说明 1. **线程池模型** - 预创建16个工作线程处理请求 - 使用互斥锁(`pthread_mutex_t`)和条件变量(`pthread_cond_t`)实现线程安全队列 - 避免线程频繁创建销毁的开销 2. **高并发实现** ```c // 使用epoll实现I/O多路复用 int epoll_fd = epoll_create1(0); epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev); // 边缘触发模式(EPOLLET)提高效率 ev.events = EPOLLIN | EPOLLET; ``` 3. **零拷贝文件传输** ```c // 使用sendfile系统调用高效传输文件 sendfile(client_fd, fd, NULL, st.st_size); ``` 4. **安全防护** ```c // 防止路径遍历攻击 int is_safe_path(const char *path) { if (strstr(path, "..") != NULL) return 0; if (strstr(path, "/etc/passwd") != NULL) return 0; return 1; } ``` 5. **HTTP解析** - 使用`sscanf`解析请求行 - 自动处理POST请求的内容长度 - 支持multipart/form-data文件上传 ### 编译与测试 ```bash # 编译 gcc -o webserver server.c -lpthread # 运行 ./webserver # 测试GET请求 curl http://localhost:8080/ curl http://localhost:8080/index.html # 测试POST请求 curl -X POST -d "name=value" http://localhost:8080/submit # 测试文件上传 curl -F "file=@test.txt" http://localhost:8080/upload ``` ### 性能优化建议 1. **连接复用**:实现HTTP Keep-Alive减少连接开销 2. **内存池**:预分配请求/响应内存减少`malloc`调用 3. **日志系统**:添加异步日志记录功能 4. **配置文件**:支持端口、线程数等参数配置 5. **SSL支持**:添加OpenSSL实现HTTPS
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值