GNU Libmicrohttpd简介

杨骁
2023-12-01

GNU Libmicrohttpd是一个用来在项目中内嵌http服务器的C语言库。这是一款免费软件,并且是GNU项目的一部分。 它具有以下几个鲜明的特点:

  • C语言库,小而快
  • 简易的API,且都是可重入的
  • 支持 HTTP 1.1 可以同时侦听多个端口
  • 四种不同的线程模式(select、poll、pthread、thread pool)
  • 库平台支持 GNU/Linux, FreeBSD, OpenBSD, NetBSD, Android, Darwin (macOS), W32, OpenIndiana/Solaris, z/OS 等
  • 支持IPv6、SHOUTcast等
  • 生成的二制文件只有32K(不包含TLS/SSL等额外功能)

主页: http://www.gnu.org/software/libmicrohttpd/
Git: https://git.gnunet.org/libmicrohttpd.git
HTTP(S):https://ftp.gnu.org/gnu/libmicrohttpd/
FTP: ftp://ftp.gnu.org/gnu/libmicrohttpd/

编译与安装

libmicrohttpd 有提供编译好的二进制,可自行通过HTTP(S)或者FTP方式下载

官方编译版本使用的是 MT 选项,如果需要 MD,或者其他编译选项,可以下载源码,根据不同平台进行编译。

接口说明

Main API

//创建服务实例
MHD_start_daemon
//允许现有连接,不允许新的连接加入
MHD_quiesce_daemon
//结束服务实例
MHD_stop_daemon
//Add another client connection to the set of connections
MHD_add_connection
//获得当前进程select集合
MHD_get_fdset
//获得poll函数的超时时间
MHD_get_timeout
//启动对文件句柄的监督
MHD_run
//启动服务&select poll
MHD_run_from_select

Connection API

//获取连接的所有头信息
MHD_get_connection_values
//设置每个连接的头
MHD_set_connection_value
//获取错误的信息
MHD_set_panic_func
//处理转移字符
MHD_http_unescape
//查询一个连接中的头字段
MHD_lookup_connection_value
//把respons 放入返回队列
MHD_queue_response
//挂起连接
MHD_suspend_connection
//回复连接
MHD_resume_connection

Response API

//设置response参数
MHD_set_response_options

//通过回调创建Response
MHD_create_response_from_callback
//通过data指针创建返回结果
MHD_create_response_from_data
//通过buffer创建返回结果
MHD_create_response_from_buffer
//通过buffer和回调创建返回结果
MHD_create_response_from_buffer_with_free_callback
//通过文件描述符来创建返回结果
MHD_create_response_from_fd
//升级协议,容许我们自己多一些事情(如websocket)
MHD_upgrade_action
//创建升级协议时的返回内容
MHD_create_response_for_upgrade
//销毁response
MHD_destroy_response
//为response添加Header
MHD_add_response_header
//在response中添加页脚
MHD_add_response_footer
//在response中删除头
MHD_del_response_header
//获取
MHD_get_response_headers

PostProcessor functions

MHD_create_post_processor
MHD_post_process
MHD_destroy_post_processor

Digest Authentication functions

MHD_digest_auth_get_username
MHD_digest_auth_check
MHD_queue_auth_fail_response
MHD_basic_auth_get_username_password

generic query functions

MHD_get_connection_info
MHD_set_connection_option
MHD_get_daemon_info
MHD_get_version
MHD_is_feature_supported

示例

以下是GNU/Linux(包括在发行版中)系统下的一个简单示例:

#include <microhttpd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define PAGE "<html><head><title>libmicrohttpd demo</title>"\
             "</head><body>libmicrohttpd demo</body></html>"

static int ahc_echo(void * cls,
                    struct MHD_Connection * connection,
                    const char * url,
                    const char * method,
                    const char * version,
                    const char * upload_data,
                    size_t * upload_data_size,
                    void ** ptr) {
  static int dummy;
  const char * page = cls;
  struct MHD_Response * response;
  int ret;

  if (0 != strcmp(method, "GET"))
    return MHD_NO; /* unexpected method */
  if (&dummy != *ptr)
    {
      /* The first time only the headers are valid,
         do not respond in the first round... */
      *ptr = &dummy;
      return MHD_YES;
    }
  if (0 != *upload_data_size)
    return MHD_NO; /* upload data in a GET!? */
  *ptr = NULL; /* clear context pointer */
  response = MHD_create_response_from_buffer (strlen(page),
                                              (void*) page,
                                              MHD_RESPMEM_PERSISTENT);
  ret = MHD_queue_response(connection,
                          MHD_HTTP_OK,
                          response);
  MHD_destroy_response(response);
  return ret;
}

int main(int argc,
        char ** argv) {
  struct MHD_Daemon * d;
  if (argc != 2) {
    printf("%s PORT\n", argv[0]);
    return 1;
  }
  d = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION,
                      atoi(argv[1]),
                      NULL,
                      NULL,
                      &ahc_echo,
                      PAGE,
                      MHD_OPTION_END);
  if (d == NULL)
    return 1;
  (void) getc (stdin);
  MHD_stop_daemon(d);
  return 0;
}

上面的例子使用最简单的线程模式MHD_USE_THREAD_PER_CONNECTION。在这种模式下,MHD启动一个线程在端口上侦听新连接,然后生成一个新线程来处理每个连接。如果HTTP服务器几乎没有连接之间共享的任何状态(没有同步问题!),则此模式非常有用。并且该模式可能需要执行阻止操作(例如扩展的IO或代码运行)来处理单个连接。

可以看到MHD_start_daemon函数启动服务,并且需要一个回调入参 ahc_echo,注意此方法是非阻塞的,此示例使用getc函数等待用户输入终止。

ahc_echo回到函数在每次请求的时候都会进入。使用MHD_create_response_from_buffer函数创建一个响应结构体,然后使用MHD_queue_response函数把响应结构推入响应队列,libmicrohttpd 会自动执行响应。最后使用MHD_destroy_response清理内存。

main函数

main函数非常简单,核心调用只有2个函数:MHD_start_daemonMHD_stop_daemon,分别开始和停止http服务器。

int main()
{
	const int port = 8888;

	struct MHD_Daemon* daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, port , NULL, NULL,
	 												connectionHandler, NULL, MHD_OPTION_END);

	if (daemon == NULL) {	
		std::cout << "cannot start server!\n";	
		return -1;	
	}
	
	std::cin.get();	
	MHD_stop_daemon(daemon);
	
	return 0;	
}

MHD_start_daemon函数包含非常多的参数,这也意味着它集成了很多的功能,这里只关注四个参数,其它都为NULL:

  1. MHD_USE_INTERNAL_POLLING_THREAD。这个参数与其他两个参数(MHD_USE_POLL_INTERNAL_THREAD、MHD_USE_EPOLL_INTERNAL_THREAD)一起构成了microhttpd支持的三种模式:select、poll、epoll。用户必须选择其中之一。具体信息见源码。
  2. port。端口号。
  3. connectHandler。处理请求的函数。
  4. MHD_OPTION_END。由于MHD_start_daemon最后一个参数是一个变参,因此MHD_OPTION_END用来表示变参终止。

请求处理函数

所有的请求处理都发生在connectionHandler中:

int connectionHandler( void *cls, struct MHD_Connection *connection, const char *url, 
						const char *method, const char *version, const char *upload_data, 
						size_t *upload_data_size, void **con_cls)
{
	const char* pageBuffer = "<html><body>Hello, I'm lgxZJ!</body></html>";
	struct MHD_Response *response; 

	response = MHD_create_response_from_buffer(strlen(pageBuffer), (void*)pageBuffer,
												MHD_RESPMEM_PERSISTENT);

	if (MHD_add_response_header(response, "Content-Type", "text/html") == MHD_NO) {
		std::cout << "MHD_add_response_header error\n";
		return MHD_NO;
	}

	if (MHD_queue_response(connection, MHD_HTTP_OK, response) == MHD_NO) {
		std::cout << "MHD_queue_response error\n";
		return MHD_NO;
	}

	MHD_destroy_response(response);
	return MHD_YES;
}

这个函数签名包含了所有用来处理请求的有用信息。microhttpd库提供了函数来方便我们响应请求,这里重点看创建响应。microhttpd库提供了两种方法来创建请求:从buffer创建、从文件创建。但是后者需要传入一个文件描述符,这在windows上不是很方便。

这里用缓冲创建。需要注意的是最后一个参数,这是一个MHD_ResponseMemoryMode枚举值,表示使用的buffer内容是固定不变的。这种枚举类型还包含其他2种代表瞬时缓冲类型的值,分别表示缓冲区是在heap上的,和非heap(例如stack)上的。 用不同的缓冲区时要记得用不同的枚举值。 接下来设置MIME类型,把缓冲入队,并释放MHD_Response结构体。对于正确响应,返回MHD_YES;不能响应的,返回MHD_NO

结果返回函数

创建返回的response,在抛给库后进行销毁。

static enum MHD_Result
send_page (struct MHD_Connection *connection, const char *page)
{
	enum MHD_Result ret;
	struct MHD_Response *response;
	response = MHD_create_response_from_buffer (strlen (page), (void *) page,
												MHD_RESPMEM_PERSISTENT);
	
	if (! response)
		return MHD_NO;
	
	ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
	MHD_destroy_response (response);
	return ret;
}

销毁函数

用户销毁开辟的相应内存以及相应资源。

static void
request_completed (void *cls, struct MHD_Connection *connection, 
				   void **con_cls, enum MHD_RequestTerminationCode toe)
{
	struct connection_info_struct *con_info = *con_cls;
	
	if (NULL == con_info)
		return;
		
	if (con_info->connectiontype == POST)
	{
		MHD_destroy_post_processor (con_info->postprocessor);
		
		if (con_info->answerstring)
			free (con_info->answerstring);
	}
	
	free (con_info);
	*con_cls = NULL;
}

创建服务daemon

通过相关接口创建服务实例

struct MHD_Daemon *
MHD_start_daemon (unsigned int flags,
                  uint16_t port,
                  MHD_AcceptPolicyCallback apc,
                  void *apc_cls,
                  MHD_AccessHandlerCallback dh,
                  void *dh_cls,
                  ...)
{
  struct MHD_Daemon *daemon;
  va_list ap;

  va_start (ap,
            dh_cls);
  daemon = MHD_start_daemon_va (flags,
                                port,
                                apc,
                                apc_cls,
                                dh,
                                dh_cls,
                                ap);
  va_end (ap);
  return daemon;
}
 类似资料: