前言

TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.net/,源代码下载:https://sourceforge.net/projects/tinyhttpd/,因为是学习型的代码,已经有好多年没更新了,也没什么更新必要,整个代码才500多行,10多个函数,对于学习HTTP服务器的原理来说非常有帮助,把代码读一遍,再按照执行处理流程调试一下,基本上可以搞清楚Web服务器在收到静态页面请求和CGI请求的一些基本处理逻辑。源代码的注释我这里就不讲了,本身代码比较简单,而且网上这样的文章汗牛充栋,可以去后面的参考文档阅读下。

本文主要是将TinyHTTPd进行一些简单移植,使其可以在Windows上面运行调试,让只有Windows开发调试环境的小伙伴也能够学习学习。

修改明细

支持Windows部分

1、  Windows下的socket支持(微小改动,变量,宏调整等等)。

2、  接入基于Windows平台的一个微型线程池库(项目在用),对于每个新的http请求抛到线程池处理,源代码使用的是基于Linux的pthread。

3、  部分字符串比较函数修改为windows平台的对应支持函数,以及其它一些相应兼容性修改。

4、  CGI部分支持了Python脚本和Windows批处理脚本,其它的如果需要支持可以进行修改,另外,CGI部分目前实现比较粗糙,完全是为了体现一下CGI请求的原理(POST的CGI处理仅仅是把提交的数据返回给客户端显示)。

5、  CGI部分使用了匿名管道,匿名管道不支持异步读写数据,因此需要控制读写匿名管道的次数(建议仅读一次,并且CGI的返回字符长度不要超过2048字节)。

优化

1、  给客户端返回数据时,合并了需要发送的数据,使用send一次发送,而不是每次发送几个字符,不过这里没有进行send失败的错误处理,学习代码吧,如果是商用代码,send失败是需要重试的(当然,商用代码一般都是使用异步socket),这个地方之前作者分成多次发送的目的可能是为了体现网络数据传输的原理。

2、  合并了一些公用代码。

3、  代码里面直接写死了绑定80端口,如果需要由系统自动分配端口就把这句代码:u_short port = 80修改为u_short port = 0,绑死80端口是为了使用浏览器测试时比较方便。

bug修改

1、  cat函数里面使用fgets读取文件进行数据发送时,有可能发送不完整。

资源补充

1、  补充批处理的cgi支持和py脚本的cgi支持(都比较简单,需要注意的是py脚本支持需要本地安装python2.x的环境)。

测试情况

主页

URL:http://127.0.0.1/

其它静态页面

URL:http://127.0.0.1/detect.html

python CGI

URL:http://127.0.0.1/cgipy?p.py

批处理 CGI

URL:http://127.0.0.1/cgibat?p.bat

POST CGI

URL:http://127.0.0.1/index.html

源代码

本来不想帖代码的,还是贴一点吧,工程下载请点这里

  1. /* -------------------------------------------------------------------------
  2. //  文件名     :   tinyhttp.cpp
  3. //  创建者     :   magictong
  4. //  创建时间    :   2016/11/16 17:13:55
  5. //  功能描述    :   support windows of tinyhttpd, use mutilthread...
  6. //
  7. //  $Id: $
  8. // -----------------------------------------------------------------------*/
  9. /* J. David's webserver */
  10. /* This is a simple webserver.
  11. * Created November 1999 by J. David Blackstone.
  12. * CSE 4344 (Network concepts), Prof. Zeigler
  13. * University of Texas at Arlington
  14. */
  15. /* This program compiles for Sparc Solaris 2.6.
  16. * To compile for Linux:
  17. *  1) Comment out the #include <pthread.h> line.
  18. *  2) Comment out the line that defines the variable newthread.
  19. *  3) Comment out the two lines that run pthread_create().
  20. *  4) Uncomment the line that runs accept_request().
  21. *  5) Remove -lsocket from the Makefile.
  22. */
  23. #include "stdafx.h"
  24. #include "windowcgi.h"
  25. #include "ThreadProc.h"
  26. #include <stdio.h>
  27. #include <ctype.h>
  28. #include <stdlib.h>
  29. #include <string.h>
  30. #include <sys/stat.h>
  31. #include <sys/types.h>
  32. #include <WinSock2.h>
  33. #pragma comment(lib, "wsock32.lib")
  34. #pragma warning(disable : 4267)
  35. #define ISspace(x) isspace((int)(x))
  36. #define SERVER_STRING "Server: tinyhttp /0.1.0\r\n"
  37. // -------------------------------------------------------------------------
  38. // -------------------------------------------------------------------------
  39. // 类名       : CTinyHttp
  40. // 功能       :
  41. // 附注       :
  42. // -------------------------------------------------------------------------
  43. class CTinyHttp
  44. {
  45. public:
  46. typedef struct tagSocketContext
  47. {
  48. SOCKET socket_Client;
  49. tagSocketContext() : socket_Client(-1) {}
  50. } SOCKET_CONTEXT, *PSOCKET_CONTEXT;
  51. /**********************************************************************/
  52. /* A request has caused a call to accept() on the server port to
  53. * return.  Process the request appropriately.
  54. * Parameters: the socket connected to the client */
  55. /**********************************************************************/
  56. void accept_request(nilstruct&, SOCKET_CONTEXT& socket_context)
  57. {
  58. printf("Tid[%u] accept_request\n", (unsigned int)::GetCurrentThreadId());
  59. #ifdef _DEBUG
  60. // 测试是否可以并发
  61. ::Sleep(200);
  62. #endif
  63. char buf[1024] = {0};
  64. int numchars = 0;
  65. char method[255] = {0};
  66. char url[255] = {0};
  67. char path[512] = {0};
  68. int i = 0, j = 0;
  69. struct stat st;
  70. int cgi = 0;      /* becomes true if server decides this is a CGI program */
  71. char* query_string = NULL;
  72. SOCKET client = socket_context.socket_Client;
  73. numchars = get_line(client, buf, sizeof(buf));
  74. // 获取HTTP的请求方法名
  75. while (j < numchars && !ISspace(buf[j]) && (i < sizeof(method) - 1))
  76. {
  77. method[i] = buf[j];
  78. i++; j++;
  79. }
  80. method[i] = '\0';
  81. if (_stricmp(method, "GET") != 0 && _stricmp(method, "POST"))      // 只处理GET请求
  82. {
  83. if (numchars > 0)
  84. {
  85. discardheaders(client);
  86. }
  87. unimplemented(client);
  88. closesocket(client);
  89. return;
  90. }
  91. if (_stricmp(method, "POST") == 0)
  92. cgi = 1; // POST请求,当成CGI处理
  93. // 获取到URL路径,存放到url字符数组里面
  94. i = 0;
  95. while (ISspace(buf[j]) && (j < sizeof(buf)))
  96. {
  97. j++;
  98. }
  99. while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
  100. {
  101. url[i] = buf[j];
  102. i++;
  103. j++;
  104. }
  105. url[i] = '\0';
  106. if (_stricmp(method, "GET") == 0)
  107. {
  108. query_string = url;
  109. while ((*query_string != '?') && (*query_string != '\0'))
  110. query_string++;
  111. if (*query_string == '?')
  112. {
  113. // URL带参数,当成CGI处理
  114. cgi = 1;
  115. *query_string = '\0';
  116. query_string++;
  117. }
  118. }
  119. sprintf_s(path, 512, "htdocs%s", url);
  120. if (path[strlen(path) - 1] == '/')
  121. {
  122. // 补齐
  123. strcat_s(path, 512, "index.html");
  124. }
  125. if (stat(path, &st) == -1)
  126. {
  127. // 文件不存在
  128. if (numchars > 0)
  129. {
  130. discardheaders(client);
  131. }
  132. not_found(client);
  133. }
  134. else
  135. {
  136. // 如果是文件夹则补齐
  137. if ((st.st_mode & S_IFMT) == S_IFDIR)
  138. strcat_s(path, 512, "/index.html");
  139. if (st.st_mode & S_IEXEC)
  140. cgi = 1; // 具有可执行权限
  141. if (!cgi)
  142. {
  143. serve_file(client, path);
  144. }
  145. else
  146. {
  147. execute_cgi(client, path, method, query_string);
  148. }
  149. }
  150. closesocket(client);
  151. }
  152. /**********************************************************************/
  153. /* Execute a CGI script.  Will need to set environment variables as
  154. * appropriate.
  155. * Parameters: client socket descriptor
  156. *             path to the CGI script */
  157. /**********************************************************************/
  158. void execute_cgi(SOCKET client, const char *path, const char* method, const char* query_string)
  159. {
  160. char buf[1024] = {0};
  161. int cgi_output[2] = {0};
  162. int cgi_input[2] = {0};
  163. int i = 0;
  164. char c = 0;
  165. int numchars = 1;
  166. int content_length = -1;
  167. buf[0] = 'A'; buf[1] = '\0';
  168. if (_stricmp(method, "GET") == 0)
  169. {
  170. discardheaders(client);
  171. }
  172. else    /* POST */
  173. {
  174. numchars = get_line(client, buf, sizeof(buf));
  175. while ((numchars > 0) && strcmp("\n", buf))
  176. {
  177. buf[15] = '\0';
  178. if (_stricmp(buf, "Content-Length:") == 0)
  179. {
  180. content_length = atoi(&(buf[16]));
  181. }
  182. numchars = get_line(client, buf, sizeof(buf));
  183. }
  184. if (content_length == -1)
  185. {
  186. bad_request(client);
  187. return;
  188. }
  189. }
  190. CWinCGI cgi;
  191. if (!cgi.Exec(path, query_string))
  192. {
  193. bad_request(client);
  194. return;
  195. }
  196. //SOCKET client, const char *path, const char* method, const char* query_string
  197. if (_stricmp(method, "POST") == 0)
  198. {
  199. for (i = 0; i < content_length; i++)
  200. {
  201. recv(client, &c, 1, 0);
  202. cgi.Write((PBYTE)&c, 1);
  203. }
  204. c = '\n';
  205. cgi.Write((PBYTE)&c, 1);
  206. }
  207. cgi.Wait();
  208. char outBuff[2048] = {0};
  209. cgi.Read((PBYTE)outBuff, 2047);
  210. send(client, outBuff, strlen(outBuff), 0);
  211. }
  212. /**********************************************************************/
  213. /* Put the entire contents of a file out on a socket.  This function
  214. * is named after the UNIX "cat" command, because it might have been
  215. * easier just to do something like pipe, fork, and exec("cat").
  216. * Parameters: the client socket descriptor
  217. *             FILE pointer for the file to cat */
  218. /**********************************************************************/
  219. void cat(SOCKET client, FILE *resource)
  220. {
  221. char buf[1024] = {0};
  222. do
  223. {
  224. fgets(buf, sizeof(buf), resource);
  225. size_t len = strlen(buf);
  226. if (len > 0)
  227. {
  228. send(client, buf, len, 0);
  229. }
  230. } while (!feof(resource));
  231. }
  232. /**********************************************************************/
  233. /* Print out an error message with perror() (for system errors; based
  234. * on value of errno, which indicates system call errors) and exit the
  235. * program indicating an error. */
  236. /**********************************************************************/
  237. void error_die(const char *sc)
  238. {
  239. perror(sc);
  240. exit(1);
  241. }
  242. /**********************************************************************/
  243. /* Get a line from a socket, whether the line ends in a newline,
  244. * carriage return, or a CRLF combination.  Terminates the string read
  245. * with a null character.  If no newline indicator is found before the
  246. * end of the buffer, the string is terminated with a null.  If any of
  247. * the above three line terminators is read, the last character of the
  248. * string will be a linefeed and the string will be terminated with a
  249. * null character.
  250. * Parameters: the socket descriptor
  251. *             the buffer to save the data in
  252. *             the size of the buffer
  253. * Returns: the number of bytes stored (excluding null) */
  254. /**********************************************************************/
  255. int get_line(SOCKET sock, char *buf, int size)
  256. {
  257. int i = 0;
  258. char c = '\0';
  259. int n;
  260. while ((i < size - 1) && (c != '\n'))
  261. {
  262. n = recv(sock, &c, 1, 0);
  263. /* DEBUG printf("%02X\n", c); */
  264. if (n > 0)
  265. {
  266. if (c == '\r')
  267. {
  268. n = recv(sock, &c, 1, MSG_PEEK);
  269. /* DEBUG printf("%02X\n", c); */
  270. if ((n > 0) && (c == '\n'))
  271. {
  272. recv(sock, &c, 1, 0);
  273. }
  274. else
  275. {
  276. c = '\n';
  277. }
  278. }
  279. buf[i] = c;
  280. i++;
  281. }
  282. else
  283. {
  284. c = '\n';
  285. }
  286. }
  287. buf[i] = '\0';
  288. return(i);
  289. }
  290. /**********************************************************************/
  291. /* Return the informational HTTP headers about a file. */
  292. /* Parameters: the socket to print the headers on
  293. *             the name of the file */
  294. /**********************************************************************/
  295. void headers(SOCKET client, const char *filename)
  296. {
  297. (void)filename;
  298. char* pHeader = "HTTP/1.0 200 OK\r\n"\
  299. SERVER_STRING \
  300. "Content-Type: text/html\r\n\r\n";
  301. send(client, pHeader, strlen(pHeader), 0);
  302. }
  303. /**********************************************************************/
  304. /* Give a client a 404 not found status message. */
  305. /**********************************************************************/
  306. void not_found(SOCKET client)
  307. {
  308. char* pResponse = "HTTP/1.0 404 NOT FOUND\r\n"\
  309. SERVER_STRING \
  310. "Content-Type: text/html\r\n\r\n"\
  311. "<HTML><TITLE>Not Found</TITLE>\r\n"\
  312. "<BODY><P>The server could not fulfill\r\n"\
  313. "your request because the resource specified\r\n"\
  314. "is unavailable or nonexistent.\r\n"\
  315. "</BODY></HTML>\r\n";
  316. send(client, pResponse, strlen(pResponse), 0);
  317. }
  318. /**********************************************************************/
  319. /* Inform the client that the requested web method has not been
  320. * implemented.
  321. * Parameter: the client socket */
  322. /**********************************************************************/
  323. void unimplemented(SOCKET client)
  324. {
  325. char* pResponse = "HTTP/1.0 501 Method Not Implemented\r\n"\
  326. SERVER_STRING \
  327. "Content-Type: text/html\r\n\r\n"\
  328. "<HTML><HEAD><TITLE>Method Not Implemented\r\n"\
  329. "</TITLE></HEAD>\r\n"\
  330. "<BODY><P>HTTP request method not supported.</P>\r\n"\
  331. "</BODY></HTML>\r\n";
  332. send(client, pResponse, strlen(pResponse), 0);
  333. }
  334. /**********************************************************************/
  335. /* Inform the client that a CGI script could not be executed.
  336. * Parameter: the client socket descriptor. */
  337. /**********************************************************************/
  338. void cannot_execute(SOCKET client)
  339. {
  340. char* pResponse = "HTTP/1.0 500 Internal Server Error\r\n"\
  341. "Content-Type: text/html\r\n\r\n"\
  342. "<P>Error prohibited CGI execution.</P>\r\n";
  343. send(client, pResponse, strlen(pResponse), 0);
  344. }
  345. /**********************************************************************/
  346. /* Inform the client that a request it has made has a problem.
  347. * Parameters: client socket */
  348. /**********************************************************************/
  349. void bad_request(SOCKET client)
  350. {
  351. char* pResponse = "HTTP/1.0 400 BAD REQUEST\r\n"\
  352. "Content-Type: text/html\r\n\r\n"\
  353. "<P>Your browser sent a bad request, such as a POST without a Content-Length.</P>\r\n";
  354. send(client, pResponse, strlen(pResponse), 0);
  355. }
  356. /**********************************************************************/
  357. /* Send a regular file to the client.  Use headers, and report
  358. * errors to client if they occur.
  359. * Parameters: a pointer to a file structure produced from the socket
  360. *              file descriptor
  361. *             the name of the file to serve */
  362. /**********************************************************************/
  363. void serve_file(SOCKET client, const char *filename)
  364. {
  365. FILE *resource = NULL;
  366. discardheaders(client);
  367. fopen_s(&resource, filename, "r");
  368. if (resource == NULL)
  369. {
  370. not_found(client);
  371. }
  372. else
  373. {
  374. headers(client, filename);
  375. cat(client, resource);
  376. }
  377. fclose(resource);
  378. }
  379. // -------------------------------------------------------------------------
  380. // 函数       : discardheaders
  381. // 功能       : 清除http头数据(从网络中全部读出来)
  382. // 返回值  : void
  383. // 参数       : SOCKET client
  384. // 附注       :
  385. // -------------------------------------------------------------------------
  386. void discardheaders(SOCKET client)
  387. {
  388. char buf[1024] = {0};
  389. int numchars = 1;
  390. while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
  391. {
  392. numchars = get_line(client, buf, sizeof(buf));
  393. }
  394. }
  395. /**********************************************************************/
  396. /* This function starts the process of listening for web connections
  397. * on a specified port.  If the port is 0, then dynamically allocate a
  398. * port and modify the original port variable to reflect the actual
  399. * port.
  400. * Parameters: pointer to variable containing the port to connect on
  401. * Returns: the socket */
  402. /**********************************************************************/
  403. SOCKET startup(u_short* port)
  404. {
  405. SOCKET httpd = 0;
  406. struct sockaddr_in name = {0};
  407. httpd = socket(AF_INET, SOCK_STREAM, 0);
  408. if (httpd == INVALID_SOCKET)
  409. {
  410. error_die("startup socket");
  411. }
  412. name.sin_family = AF_INET;
  413. name.sin_port = htons(*port);
  414. name.sin_addr.s_addr = inet_addr("127.0.0.1");
  415. if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
  416. {
  417. error_die("startup bind");
  418. }
  419. if (*port == 0)  /* if dynamically allocating a port */
  420. {
  421. int namelen = sizeof(name);
  422. if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
  423. {
  424. error_die("getsockname");
  425. }
  426. *port = ntohs(name.sin_port);
  427. }
  428. if (listen(httpd, 5) < 0)
  429. {
  430. error_die("listen");
  431. }
  432. return httpd;
  433. }
  434. }; // End Class CTinyHttp
  435. int _tmain(int argc, _TCHAR* argv[])
  436. {
  437. SOCKET server_sock = INVALID_SOCKET;
  438. //u_short port = 0;
  439. u_short port = 80;
  440. struct sockaddr_in client_name = {0};
  441. int client_name_len = sizeof(client_name);
  442. typedef CMultiTaskThreadPoolT<CTinyHttp, CTinyHttp::SOCKET_CONTEXT, nilstruct, 5, CComMultiThreadModel::AutoCriticalSection> CMultiTaskThreadPool;
  443. CTinyHttp tinyHttpSvr;
  444. // init socket
  445. WSADATA wsaData = {0};
  446. WSAStartup(MAKEWORD(2, 2), &wsaData);
  447. server_sock = tinyHttpSvr.startup(&port);
  448. printf("httpd running on port: %d\n", port);
  449. CMultiTaskThreadPool m_threadpool(&tinyHttpSvr, &CTinyHttp::accept_request);
  450. while (1)
  451. {
  452. CTinyHttp::SOCKET_CONTEXT socket_context;
  453. socket_context.socket_Client = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len);
  454. if (socket_context.socket_Client == INVALID_SOCKET)
  455. {
  456. tinyHttpSvr.error_die("accept");
  457. }
  458. printf("Tid[%u] accetp new connect: %u\n", (unsigned int)::GetCurrentThreadId(), (unsigned int)socket_context.socket_Client);
  459. m_threadpool.AddTask(socket_context);
  460. }
  461. // can not to run this
  462. m_threadpool.EndTasks();
  463. closesocket(server_sock);
  464. WSACleanup();
  465. return 0;
  466. }
  467. // -------------------------------------------------------------------------
  468. // $Log: $

参考文档

[1] tinyhttp源码阅读(注释) http://www.cnblogs.com/oloroso/p/5459196.html

[2] 【源码剖析】tinyhttpd —— C 语言实现最简单的 HTTP 服务器http://blog.csdn.NET/jcjc918/article/details/42129311

[3] Tinyhttp源码分析http://blog.csdn.net/yzhang6_10/article/details/51534409

[4] tinyhttpd源码详解http://blog.csdn.Net/baiwfg2/article/details/45582723

[5] CGI介绍 http://www.jdon.com/idea/cgi.htm

http://blog.csdn.net/magictong/article/details/53201038

Tinyhttpd for Windows(学习型的项目,才500多行代码)的更多相关文章

  1. 安卓开发学习经历2--《第一行代码》coolweather项目SQL语句同一个“陷阱”掉两次 注意转义字符等特殊字符正确书写 关于Id字段自增加体会

    今天,在运行<第一行代码>coolweather第二阶段代码,又一次报错,还是神奇地与昨天相似,提示,city_id字段不存在,这里我有两种理解,一种是sql语句出错了,另外一种是没有获取 ...

  2. Tinyhttpd for Windows

    TinyHTTPd forWindows 前言 TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.net/,源代码下载:h ...

  3. Tinyhttpd for Windows(500多行)

    TinyHTTPd forWindows 前言 TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.NET/,源代码下载:h ...

  4. git入门学习(一):github for windows上传本地项目到github

    Git是目前最先进的分布式版本控制系统,作为一个程序员,我们需要掌握其用法.Github发布了Github for Windows 则大大降低了学习成本和使用难度,他甚至比SVN都简单. 一.首先在g ...

  5. Windows服务、批处理项目实战

    一周一话题之三(Windows服务.批处理项目实战)   -->目录导航 一. Windows服务 1. windows service介绍 2. 使用步骤 3. 项目实例--数据上传下载服务 ...

  6. vue学习(1) vue-cli 项目搭建

    vue学习(1)  vue-cli 项目搭建 一.windows环境 1. 下载node.js安装包 官网:https://nodejs.org/en/download/ 选择LTS下载 2. 安装 ...

  7. 学习Coding-iOS开源项目日志(五)

    继续,接着前面第四篇<学习Coding-iOS开源项目日志(四)>讲解Coding-iOS开源项目. 前 言:作为初级程序员,想要提高自己的水平,其中一个有效的学习方法就是学习别人好的项目 ...

  8. 安卓开发学习历程1——《第一行代码》coolweather项目setOnItemClickListener函数,Sql语句修改对模拟app程序机影响

    今天,将<第一行代码>最后实战的coolweather项目,认真做了一遍. 今晚,在书中第一阶段开发代码认眞在Android studio敲完,发现setOnItemClickListen ...

  9. Android Testing学习02 HelloTesting 项目建立与执行

    Android Testing学习02 HelloTesting 项目建立与执行 Android测试,分为待测试的项目和测试项目,这两个项目会生成两个独立的apk,但是内部,它们会共享同一个进程. 下 ...

随机推荐

  1. 【u017】请柬

    Time Limit: 1 second Memory Limit: 128 MB [问题描述] 在电视时代,没有多少人观看戏剧表演.Malidinesia古董喜剧演员意识到这一事实,他们想宣传剧院, ...

  2. 超级简单的Android Studio jni 实现(无需命令行)

    1.配置Anroid Studio(这步是关键) 使用[command+,] 打开Preferences,选择External Tools,点击加号框如下图: Paste_Image.png 点击+号 ...

  3. win7注册表常用设置

    win7注册表常用设置 一.总结 一句话总结:regedit可以修改很多东西,电脑时间,背景,u盘读写,鼠标右键情况. 二.win7注册表常用设置 一. 秀出自我风格的屏幕保护画面 1.气泡屏幕保护 ...

  4. mybatis结合log4j打印SQL日志

    mybatis结合log4j打印SQL日志 1.Maven引用jar包 默认的mybatis不能打印出SQL日志,不便于查看调试,须要结合log4jdbc-log4j2就能够完整的输入SQL的调试信息 ...

  5. 小强的HTML5移动开发之路(25)—— AppCan开发环境

    一.工具下载: 官方下载地址:http://www.appcan.cn/experience.html 按照安装指导默认安装即可 二.建立项目 启动AppCan SDK 建立项目 填入在appcan官 ...

  6. 在Windows中安装MinGW-w64(有图,一步一步)

    在Windows中安装MinGW-w64 发表回复 如需配合Sublime Text 3编译C程序, 请参考本站文章: 使用Sublime Text 3与MinGW-w64编译C语言程序 MinGW, ...

  7. Sublime Text3的react代码校验插件

    之前写前端一直用的是jshint做语法检查,但jshint不支持JSX语法,为了在React使用,需要用eslint代替它.六月份的时候为了写React Native,编辑器换过Webstorm和VS ...

  8. How to configure spring boot through annotations in order to have something similar to <jsp-config> in web.xml?

    JSP file not rendering in Spring Boot web application You will need not one but two dependencies (ja ...

  9. svg查看预览 , 鼠标控制放大缩小 , 托拉拽等

    自己写是不可能了 , 所以要借用插件 svg-panzoom.js 地址 : https://github.com/ariutta/svg-pan-zoom#demos 及常见问题https://ww ...

  10. Java--基础命名空间和相关东西(JAVA工程师必须会,不然杀了祭天)

    java.lang (提供利用 Java 编程语言进行程序设计的基础类)java.lang.annotation(提供了引用对象类,支持在某种程度上与垃圾回收器之间的交互)java.lang.inst ...