前面的哪些话

关于Nginx模块开发的博客资料,网上很多,很多。但是,每篇博客都只提要点,无法"step by step"照着做,对于初次接触Nginx开发的同学,只能像只盲目的蚂蚁瞎燥急!该篇文章没有太多技术深度,只是一步一步说明白Nginx模块的开发过程。

开发环境搭建

工欲善其事,必先利其器。个人推荐Eclipse CDT 作为IDE,原因很简单,代码提示与补全功能很全,完胜Codeblock这类...相信与否,试过就知道。

在ubuntu下搭建开发环境:

  • 安装GCC编译器
  1. apt-get install build-essential
  • 安装pcre/openssl/zlib开发库
  1. apt-get install libpcre3-dev
  2. apt-get install libssl-dev
  3. apt-get install libzip-dev

必需安装nginx核心模块依赖的pcre,openssl,zilib开发库

  • 安装JRE/Eclipse CDT
  1. apt-get install openjdk-8-jre
  2. wget http://ftp.yz.yamagata-u.ac.jp/pub/eclipse//technology/epp/downloads/release/neon/R/eclipse-cpp-neon-R-linux-gtk-x86_64.tar.gz && tzr -xzvf eclipse-cpp-neon-R-linux-gtk-x86_64.tar.gz
  • 下载nginx源码
  1. wget http://nginx.org/download/nginx-1.10.1.tar.gz && tar -xzvf nginx-1.10.1.tar.gz
  • 配置CDT Build Environment

    添加变量,值Nginx src下各模块路径,用冒号分隔,例如:
  1. /root/Workspace/nginx-1.10.1/src/core:/root/Workspace/nginx-1.10.1/src/event:/root/Workspace/nginx-1.10.1/src/http:/root/Workspace/nginx-1.10.1/src/mail:/root/Workspace/nginx-1.10.1/src/stream:/root/Workspace/nginx-1.10.1/src/os/unix

添加环境变量,创建C项目时自动作为-I选项



Nginx模块编译流程

Nginx使用configure脚本分析环境,自动生成objs结果。哪么configure如何编译第三方模块?答案是--add-module指定第三方模块目录,并将目录存为$ngx_addon_dir环境变量。执行$ngx_addon_dir/config脚本,读取模块配置。在config中的环境变量分为2种:小写的本地环境变量,大写的全局环境变量。例如:

  1. ngx_addon_name=ngx_http_mytest_module
  2. HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
  3. NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_moudle.c"
  4. CORE_LIBS="$CORE_LIBS -lpcre"
  • HTTP_MODULES中的ngx_http_mytest_module就是NGX_ADDON_SRCS中源码(如果有多个,都要写上)ngx_http_mytest_module.c中定义的ngx_module_t类型的全局变量。
  • 可见,第三方模块的入口点就是ngx_module_t类型全局变量,该变量又关联ngx_http_module_t类型static变量,与ngx_command_t类型static数组。
  • 在ngx_http_module_t中定义上下文配置nginx.conf解析的回调方法。
  • 在ngx_command_t中定义配置项处理的set回调方法。
  • Nginx的全部操作都是异步的。在上述的方法中根据需要又会使用其他handler方法。

    以上可以看成Nginx第三方模块的起式。

Upstream例子源码

  • config
  1. ngx_addon_name=ngx_http_mytest_module
  2. HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
  3. NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"
  • 源代码
  1. #include <ngx_config.h>
  2. #include <ngx_core.h>
  3. #include <ngx_http.h>
  4. #include <ngx_stream.h>
  5. typedef struct {
  6. ngx_http_upstream_conf_t upstream;
  7. } mytest_conf_t;
  8. typedef struct {
  9. ngx_http_status_t status;
  10. ngx_str_t backendServer;
  11. } mytest_ctx_t;
  12. static void *mytest_create_loc_conf(ngx_conf_t *cf);
  13. static char *mytest_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
  14. static ngx_int_t mytest_upstream_create_request(ngx_http_request_t *r);
  15. static ngx_int_t mytest_upstream_process_status_line(ngx_http_request_t *r);
  16. static ngx_int_t mytest_upstream_process_header(ngx_http_request_t *r);
  17. static void mytest_upstream_finalize_request(ngx_http_request_t *r,
  18. ngx_int_t rc);
  19. static ngx_int_t mytest_handler(ngx_http_request_t *r);
  20. static char *mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
  21. static ngx_http_module_t mytest_ctx = {
  22. NULL,
  23. NULL,
  24. NULL,
  25. NULL,
  26. NULL,
  27. NULL,
  28. mytest_create_loc_conf,
  29. mytest_merge_loc_conf
  30. };
  31. static ngx_command_t mytest_commands[] = {
  32. {
  33. ngx_string("mytest"),
  34. NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS,
  35. mytest,
  36. NGX_HTTP_LOC_CONF_OFFSET,
  37. 0,
  38. NULL
  39. },
  40. ngx_null_command
  41. };
  42. ngx_module_t ngx_http_mytest_module = {
  43. NGX_MODULE_V1,
  44. &mytest_ctx,
  45. mytest_commands,
  46. NGX_HTTP_MODULE,
  47. NULL,
  48. NULL,
  49. NULL,
  50. NULL,
  51. NULL,
  52. NULL,
  53. NULL,
  54. NGX_MODULE_V1_PADDING
  55. };
  56. static ngx_str_t mytest_upstream_hide_headers[] =
  57. {
  58. ngx_string("Date"),
  59. ngx_string("Server"),
  60. ngx_string("X-Pad"),
  61. ngx_string("X-Accel-Expires"),
  62. ngx_string("X-Accel-Redirect"),
  63. ngx_string("X-Accel-Limit-Rate"),
  64. ngx_string("X-Accel-Buffering"),
  65. ngx_string("X-Accel-Charset"),
  66. ngx_null_string
  67. };
  68. static void *mytest_create_loc_conf(ngx_conf_t *cf){
  69. mytest_conf_t *mycf;
  70. mycf = (mytest_conf_t *)ngx_pcalloc(cf->pool, sizeof(mytest_conf_t));
  71. if(mycf == NULL){
  72. return NULL;
  73. }
  74. mycf->upstream.connect_timeout = 60000;
  75. mycf->upstream.send_timeout = 60000;
  76. mycf->upstream.read_timeout = 60000;
  77. mycf->upstream.store_access = 0600;
  78. mycf->upstream.buffering = 0;
  79. mycf->upstream.bufs.num = 8;
  80. mycf->upstream.bufs.size = ngx_pagesize;
  81. mycf->upstream.buffer_size = ngx_pagesize;
  82. mycf->upstream.busy_buffers_size = 2 * ngx_pagesize;
  83. mycf->upstream.temp_file_write_size = 2 * ngx_pagesize;
  84. mycf->upstream.max_temp_file_size = 1024 * 1024 *1024;
  85. mycf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
  86. mycf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
  87. return mycf;
  88. }
  89. static char *mytest_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child){
  90. mytest_conf_t *prev = (mytest_conf_t *)parent;
  91. mytest_conf_t *conf = (mytest_conf_t *)child;
  92. ngx_hash_init_t hash;
  93. hash.max_size = 100;
  94. hash.bucket_size = 1024;
  95. hash.name = "proxy_headers_hash";
  96. if(ngx_http_upstream_hide_headers_hash(cf,&conf->upstream, &prev->upstream,mytest_upstream_hide_headers,&hash)!=NGX_OK){
  97. return NGX_CONF_ERROR;
  98. }
  99. return NGX_CONF_OK;
  100. }
  101. static ngx_int_t mytest_upstream_create_request(ngx_http_request_t *r){
  102. static ngx_str_t backendQueryLine = ngx_string("GET /search?q=%V HTTP/1.1\r\nHost: www.google.com.hk\r\nConnection: close\r\n\r\n");
  103. ngx_int_t queryLineLen = backendQueryLine.len + r->args.len - 2;
  104. ngx_buf_t *b = ngx_create_temp_buf(r->pool, queryLineLen);
  105. if(b == NULL) return NGX_ERROR;
  106. b->last = b->pos + queryLineLen;
  107. ngx_snprintf(b->pos, queryLineLen, (char *)backendQueryLine.data, &r->args);
  108. r->upstream->request_bufs = ngx_alloc_chain_link(r->pool);
  109. if(r->upstream->request_bufs == NULL) return NGX_ERROR;
  110. r->upstream->request_bufs->buf = b;
  111. r->upstream->request_bufs->next = NULL;
  112. r->upstream->request_sent = 0;
  113. r->upstream->header_sent = 0;
  114. r->header_hash = 1;
  115. return NGX_OK;
  116. }
  117. static ngx_int_t mytest_upstream_process_status_line(ngx_http_request_t *r){
  118. size_t len;
  119. ngx_int_t rc;
  120. ngx_http_upstream_t *u;
  121. mytest_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);
  122. if(ctx == NULL) return NGX_ERROR;
  123. u = r->upstream;
  124. rc = ngx_http_parse_status_line(r, &u->buffer, &ctx->status);
  125. if(rc == NGX_AGAIN) return rc;
  126. if(rc == NGX_ERROR){
  127. ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent to valid HTTP/1.0 header");
  128. r->http_version = NGX_HTTP_VERSION_9;
  129. u->state->status = NGX_HTTP_OK;
  130. return NGX_OK;
  131. }
  132. if(u->state){
  133. u->state->status = ctx->status.code;
  134. }
  135. u->headers_in.status_n = ctx->status.code;
  136. len = ctx->status.end - ctx->status.start;
  137. u->headers_in.status_line.len = len;
  138. u->headers_in.status_line.data = ngx_pcalloc(r->pool, len);
  139. if(u->headers_in.status_line.data == NULL) return NGX_ERROR;
  140. ngx_memcpy(u->headers_in.status_line.data, ctx->status.start, len);
  141. u->process_header = mytest_upstream_process_header;
  142. return mytest_upstream_process_header(r);
  143. }
  144. static ngx_int_t mytest_upstream_process_header(ngx_http_request_t *r){
  145. ngx_int_t rc;
  146. ngx_table_elt_t *h;
  147. ngx_http_upstream_header_t *hh;
  148. ngx_http_upstream_main_conf_t *umcf;
  149. umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
  150. for(;;){
  151. rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);
  152. if(rc == NGX_OK){
  153. h = ngx_list_push(&r->upstream->headers_in.headers);
  154. if(h == NULL) return NGX_ERROR;
  155. h->hash = r->header_hash;
  156. h->key.len = r->header_name_end - r->header_name_start;
  157. h->value.len = r->header_end - r->header_start;
  158. h->key.data = ngx_pcalloc(r->pool, h->key.len + 1 + h->value.len + 1 + h->key.len);
  159. if(h->key.data == NULL) return NGX_ERROR;
  160. h->value.data = h->key.data + h->key.len + 1;
  161. h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1;
  162. ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
  163. h->key.data[h->key.len]='\0';
  164. ngx_memcpy(h->value.data, r->header_start, h->value.len);
  165. h->value.data[h->value.len] = '\0';
  166. if(h->key.len == r->lowcase_index){
  167. ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
  168. }else{
  169. ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
  170. }
  171. hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len);
  172. if(hh && hh->handler(r, h, hh->offset)!=NGX_OK) return NGX_ERROR;
  173. continue;
  174. }
  175. if(rc == NGX_HTTP_PARSE_HEADER_DONE){
  176. if(r->upstream->headers_in.server == NULL){
  177. h = ngx_list_push(&r->upstream->headers_in.headers);
  178. if(h == NULL) return NGX_ERROR;
  179. h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r');
  180. ngx_str_set(&h->key, "Server");
  181. ngx_str_null(&h->value);
  182. h->lowcase_key = (u_char *)"server";
  183. }
  184. if(r->upstream->headers_in.date == NULL){
  185. h = ngx_list_push(&r->upstream->headers_in.headers);
  186. if(h == NULL) return NGX_ERROR;
  187. h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e');
  188. ngx_str_set(&h->key, "Date");
  189. ngx_str_null(&h->value);
  190. h->lowcase_key = (u_char *)"date";
  191. }
  192. return NGX_OK;
  193. }
  194. if(rc == NGX_AGAIN) return NGX_AGAIN;
  195. ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header");
  196. return NGX_HTTP_UPSTREAM_FT_INVALID_HEADER;
  197. }
  198. }
  199. static void mytest_upstream_finalize_request(ngx_http_request_t *r, ngx_int_t rc){
  200. ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "mytest_upstream_finalize_request");
  201. }
  202. static char *mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){
  203. ngx_http_core_loc_conf_t *clcf;
  204. clcf = ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module);
  205. clcf->handler = mytest_handler;
  206. return NGX_CONF_OK;
  207. }
  208. static ngx_int_t mytest_handler(ngx_http_request_t *r){
  209. mytest_ctx_t *myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);
  210. if(myctx == NULL){
  211. myctx = ngx_pcalloc(r->pool, sizeof(mytest_ctx_t));
  212. if(myctx == NULL) return NGX_ERROR;
  213. ngx_http_set_ctx(r, myctx, ngx_http_mytest_module);
  214. }
  215. if(ngx_http_upstream_create(r)!=NGX_OK){
  216. ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_upstream_create() failed");
  217. return NGX_ERROR;
  218. }
  219. mytest_conf_t *mycf = (mytest_conf_t *)ngx_http_get_module_loc_conf(r, ngx_http_mytest_module);
  220. ngx_http_upstream_t *u = r->upstream;
  221. u->conf = &mycf->upstream;
  222. u->buffering = mycf->upstream.buffering;
  223. u->resolved = (ngx_http_upstream_resolved_t *) ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));
  224. if(u->resolved == NULL){
  225. ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_pcalloc resolved error. %s", strerror(errno));
  226. return NGX_ERROR;
  227. }
  228. static struct sockaddr_in backendSockAddr;
  229. struct hostent *pHost = gethostbyname((char *)"www.google.com.hk");
  230. if(pHost == NULL){
  231. ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "gethostbyname fail. %s", strerror(errno));
  232. return NGX_ERROR;
  233. }
  234. backendSockAddr.sin_family = AF_INET;
  235. backendSockAddr.sin_port = htons((in_port_t)80);
  236. char *pDmsIP = inet_ntoa(*(struct in_addr *)(pHost->h_addr_list[0]));
  237. backendSockAddr.sin_addr.s_addr = inet_addr(pDmsIP);
  238. myctx->backendServer.data = (u_char *)pDmsIP;
  239. myctx->backendServer.len = strlen(pDmsIP);
  240. u->resolved->sockaddr = (struct sockaddr *)&backendSockAddr;
  241. u->resolved->port = htons((in_port_t)80);
  242. u->resolved->socklen = sizeof(struct sockaddr_in);
  243. u->resolved->naddrs = 1;
  244. u->create_request = mytest_upstream_create_request;
  245. u->process_header = mytest_upstream_process_status_line;
  246. u->finalize_request = mytest_upstream_finalize_request;
  247. r->main->count++;
  248. ngx_http_upstream_init(r);
  249. return NGX_DONE;
  250. }

注意:《Nginx深入解析》的demo少了这句:“u->resolved->port = htons((in_port_t)80);”,否则报错“2016/09/09 11:24:18 [error] 28352#0: *1 no port in upstream "", client: 127.0.0.1, server: localhost, request: "GET /mytest?q=test HTTP/1.1", host: "localhost"”

  • 编译脚本
  1. ./configure --prefix=/usr/local/nginx --add-module=/root/Workspace/nginx-modules/ngx_http_mytest_module --with-debug
  2. sudo make
  3. sudo make install

安装后即可到/usr/local/nginx下配置nginx.conf进行测试。

Subrequest例子源码

  • config
  1. ngx_addon_name=ngx_http_mytest_module
  2. HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
  3. NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"
  • 源代码
  1. #include <ngx_config.h>
  2. #include <ngx_core.h>
  3. #include <ngx_http.h>
  4. typedef struct {
  5. ngx_str_t stock[6];
  6. } mytest_ctx_t;
  7. static ngx_int_t mytest_subrequest_post_handler(ngx_http_request_t *r, void *data, ngx_int_t rc);
  8. static void mytest_post_handler(ngx_http_request_t *r);
  9. static ngx_int_t mytest_handler(ngx_http_request_t *r);
  10. static char *mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
  11. static ngx_http_module_t mytest_conf = {
  12. NULL,
  13. NULL,
  14. NULL,
  15. NULL,
  16. NULL,
  17. NULL,
  18. NULL,
  19. NULL
  20. };
  21. static ngx_command_t mytest_commands[] = {
  22. {
  23. ngx_string("mytest"),
  24. NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
  25. mytest,
  26. NGX_HTTP_LOC_CONF_OFFSET,
  27. 0,
  28. NULL
  29. },
  30. ngx_null_command
  31. };
  32. ngx_module_t ngx_http_mytest_module = {
  33. NGX_MODULE_V1,
  34. &mytest_conf,
  35. mytest_commands,
  36. NGX_HTTP_MODULE,
  37. NULL,
  38. NULL,
  39. NULL,
  40. NULL,
  41. NULL,
  42. NULL,
  43. NULL,
  44. NGX_MODULE_V1_PADDING
  45. };
  46. static ngx_int_t mytest_subrequest_post_handler(ngx_http_request_t *r,
  47. void *data, ngx_int_t rc) {
  48. ngx_http_request_t *pr = r->parent;
  49. mytest_ctx_t *myctx = ngx_http_get_module_ctx(pr, ngx_http_mytest_module);
  50. pr->headers_out.status = r->headers_out.status;
  51. if (r->headers_out.status == NGX_HTTP_OK) {
  52. int flag = 0;
  53. ngx_buf_t *pRecvBuf = &r->upstream->buffer;
  54. for (; pRecvBuf->pos != pRecvBuf->last; pRecvBuf->pos++) {
  55. if (*pRecvBuf->pos == ',' || *pRecvBuf->pos == '\"') {
  56. if (flag > 0) {
  57. myctx->stock[flag - 1].len = pRecvBuf->pos
  58. - myctx->stock[flag - 1].data;
  59. }
  60. flag++;
  61. myctx->stock[flag - 1].data = pRecvBuf->pos + 1;
  62. }
  63. if (flag > 6)
  64. break;
  65. }
  66. }
  67. pr->write_event_handler = mytest_post_handler;
  68. return NGX_OK;
  69. }
  70. static void mytest_post_handler(ngx_http_request_t *r){
  71. if(r->headers_out.status != NGX_HTTP_OK){
  72. ngx_http_finalize_request(r,r->headers_out.status);
  73. return;
  74. }
  75. mytest_ctx_t *myctx = ngx_http_get_module_ctx(r,ngx_http_mytest_module);
  76. ngx_str_t output_format = ngx_string("stock[%V],Today current price:%V, volumn:%V");
  77. int bodylen = output_format.len + myctx->stock[0].len + myctx->stock[1].len + myctx->stock[4].len - 6;
  78. r->headers_out.content_length_n = bodylen;
  79. ngx_buf_t *b = ngx_create_temp_buf(r->pool, bodylen);
  80. ngx_snprintf(b->pos,bodylen,(char *)output_format.data, &myctx->stock[0], &myctx->stock[1], &myctx->stock[4]);
  81. b->last = b->pos + bodylen;
  82. b->last_buf = 1;
  83. ngx_chain_t out;
  84. out.buf = b;
  85. out.next = NULL;
  86. static ngx_str_t type = ngx_string("text/plain; charset=GBK");
  87. r->headers_out.content_type = type;
  88. r->headers_out.status = NGX_HTTP_OK;
  89. r->connection->buffered |= NGX_HTTP_WRITE_BUFFERED;
  90. ngx_int_t ret = ngx_http_send_header(r);
  91. ret = ngx_http_output_filter(r, &out);
  92. ngx_http_finalize_request(r,ret);
  93. }
  94. static ngx_int_t mytest_handler(ngx_http_request_t *r){
  95. mytest_ctx_t *myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);
  96. if(myctx == NULL){
  97. myctx = ngx_pcalloc(r->pool, sizeof(mytest_ctx_t));
  98. if(myctx == NULL) return NGX_ERROR;
  99. ngx_http_set_ctx(r,myctx,ngx_http_mytest_module);
  100. }
  101. ngx_http_post_subrequest_t *psr = ngx_pcalloc(r->pool, sizeof(ngx_http_post_subrequest_t));
  102. if(psr == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
  103. psr->handler = mytest_subrequest_post_handler;
  104. psr->data = myctx;
  105. ngx_str_t sub_prefix = ngx_string("/list=");
  106. ngx_str_t sub_location;
  107. sub_location.len = sub_prefix.len + r->args.len;
  108. sub_location.data = ngx_pcalloc(r->pool, sub_location.len);
  109. ngx_snprintf(sub_location.data, sub_location.len, "%V%V", &sub_prefix, &r->args);
  110. ngx_http_request_t *sr;
  111. ngx_int_t rc = ngx_http_subrequest(r, &sub_location, NULL, &sr, psr, NGX_HTTP_SUBREQUEST_IN_MEMORY);
  112. if(rc != NGX_OK) return NGX_ERROR;
  113. return NGX_DONE;
  114. }
  115. static char *mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){
  116. ngx_http_core_loc_conf_t *clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
  117. clcf->handler = mytest_handler;
  118. return NGX_CONF_OK;
  119. }
  • 编译脚本
  1. ./configure --prefix=/usr/local/nginx --add-module=/root/Workspace/nginx-modules/ngx_http_mytest_module2 --with-debug
  2. sudo make
  3. sudo make install

手把手教你开发Nginx模块的更多相关文章

  1. 新书上线:《Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统》,欢迎大家买回去垫椅子垫桌脚

    新书上线 大家好,笔者的新书<Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统>已上线,此书内容充实.材质优良,乃家中必备垫桌脚 ...

  2. 手把手教你开发chrome扩展一:开发Chrome Extenstion其实很简单

    手把手教你开发chrome扩展一:开发Chrome Extenstion其实很简单   手把手教你开发chrome扩展一:开发Chrome Extenstion其实很简单 手把手教你开发Chrome扩 ...

  3. 开发Nginx模块

    开发Nginx模块 前面的哪些话 关于Nginx模块开发的博客资料,网上很多,很多.但是,每篇博客都只提要点,无法"step by step"照着做,对于初次接触Nginx开发的同 ...

  4. 手把手教你开发chrome扩展

    转载:http://www.cnblogs.com/walkingp/archive/2011/04/04/2003875.html 手把手教你开发chrome扩展一:开发Chrome Extenst ...

  5. 手把手教你开发Chrome扩展三:关于本地存储数据

    手把手教你开发chrome扩展一:开发Chrome Extenstion其实很简单 手把手教你开发Chrome扩展二:为html添加行为 手把手教你开发Chrome扩展三:关于本地存储数据 HTML5 ...

  6. 手把手教你开发Chrome扩展二:为html添加行为

    手把手教你开发chrome扩展一:开发Chrome Extenstion其实很简单 手把手教你开发Chrome扩展二:为html添加行为 手把手教你开发Chrome扩展三:关于本地存储数据 上一节我们 ...

  7. 手把手教你开发BLE数据透传应用程序

    如何开发BLE数据透传应用程序?什么是BLE service和characteristic?如何开发自己的service和characteristic?如何区分ATT和GATT?有没有什么工具可以对B ...

  8. 手把手教Linux驱动2-之模块参数和符号导出

    通过<手把手教Linux驱动1-模块化编程,玩转module>的学习,我们已经掌握了如何向内核加载一个模块,现在我们学习模块之间如何传递参数. 一.给模块传递参数 当我们加载一个模块到Li ...

  9. 开发Nginx模块Helloworld

    本文是对<深入理解Nginx>一书中的实例进行实战时的记录. 1模块目录结构 my_test_module/ ├── config └── ngx_http_mytest_module.c ...

随机推荐

  1. C++中string类的操作函数。

    相信使用过MFC编程的朋友对CString这个类的印象应该非常深刻吧?的确,MFC中的CString类使用起来真的非常的方便好用.但是如果离开了MFC框架,还有没有这样使用起来非常方便的类呢?答案是肯 ...

  2. mybatis-generator + mysql/ptsql

    用了mybatis-generator,我就不再想用注解了,这与我之前说的注解与XML并用是矛盾的,知识嘛,本来就是多元化的,今天喜欢这个,明天喜欢那个,哈哈,看了mybatis-generator下 ...

  3. 一个Exception catch不住的【异常】

    现象: Controller中的方法接收到前台ajax请求后开始执行, 当执行到某一行时程序终止,前台回调进Error方法. 后台明明catch了Exception异常,但异常并没有被捕获(不进cat ...

  4. Wampserver 2.5 多网站配置方法

    写在开头:本文适用于wampserver2.5版本号,和wamp的老版本号配置有语法上的差别,笔者正是由于被老版本号的配置办法给整迷糊了所以才总结了一篇针对2.5版本号的配置方法,假设您还停留在1.x ...

  5. cmake使用总结(转)---工程主目录CMakeList文件编写

    在linux 下进行开发很多人选择编写makefile 文件进行项目环境搭建,而makefile 文件依赖关系复杂,工作量很大,搞的人头很大.采用自动化的项目构建工具cmake 可以将程序员从复杂的m ...

  6. php实现 句子逆序(需求才是最好的老师)

    php实现 句子逆序(需求才是最好的老师) 一.总结 一句话总结:需求才是最好的老师. 1.str_split()和explode()的区别? explode — 使用一个字符串分割另一个字符串 3 ...

  7. href="javascript:;" href="javascript:void(0);" href="#"区别

    一.href="javascript:;" 这种用法不正确,这么用的话会出现浏览器访问"javascript:;"这个地址的现象: 二.href="j ...

  8. java nio 缓冲区(一)

      本文来自于我的个人博客:java nio 缓冲区(一) 我们以Buffer类開始对java.nio包的浏览历程.这些类是java.nio的构造基础. 这个系列中,我们将尾随<java NIO ...

  9. redis 注册为服务

    进入redis的util目录下,拷贝redis_init_script到/etc/init.d/下并重命名为redis 修改CONF,指定配置文件,我的redis配置文件为/etc/redis/red ...

  10. Java爬虫框架WebMagic入门——爬取列表类网站文章

    初学爬虫,WebMagic作为一个Java开发的爬虫框架很容易上手,下面就通过一个简单的小例子来看一下. WebMagic框架简介 WebMagic框架包含四个组件,PageProcessor.Sch ...