nweb

    技术2024-06-14  73

    您是否曾经想过运行一个小型,安全的Web服务器,而不必担心使用安装和配置可能很复杂的功能强大的Web服务器? 您是否想知道如何编写一个通过网络套接字接收传入消息的程序? 您是否曾经希望自己的Web服务器进行试验和学习? 2012年将进行进一步更新,以支持最新的Web服务器和浏览器标准以及代码更新。

    好吧,别无所求-nweb是您所需要的。 这是一个只有200行C源代码的简单Web服务器。 它以常规用户身份运行,并且无法运行任何服务器端脚本或程序,因此无法打开任何特殊特权或安全漏洞。

    本文介绍:

    nweb服务器程序提供什么 程序中C函数功能的摘要 伪代码有助于理解代码流 使用的网络套接字系统调用和其他系统调用 客户端如何操作 C源代码

    nweb仅将以下类型的文件传输到浏览器:

    扩展名为.html或.htm的静态网页 .gif,.png,.jgp或.jpeg等图形图像 压缩的二进制文件和归档文件,例如.zip,.gz和.tar

    如果您喜欢的静态文件类型不在此列表中,则只需将其添加到源代码中,然后重新编译即可。

    提供的示例文件包括C语言中的UNIX®源代码和AIX®的预编译源代码。 该源将使用IBMVisualAge®C编译器或GNU C编译器进行编译,并且应在AIX,Linux®或任何其他UNIX版本上保持不变。

    使用以下命令进行编译。 该程序使用C语言编写,不需要任何其他库或服务。 仅当您要优化代码时才需要-O2选项:

    cc –O2 nweb.c –o nweb

    nweb中的功能

    源代码中只有很少的功能,如下所述。

    log() 将消息记录到日志文件。 如果用户请求Web服务器不允许或无法完成的操作,则nweb尝试直接通知用户。 这是通过将包含错误消息的假网页返回给用户来完成的。 由于仅从子Web服务器进程中调用此函数,因此该函数可以(一旦完成)退出,并且主Web服务器进程继续允许进一步的浏览器连接请求。 如果这不是可恢复的错误,则该过程将停止。 web() 处理HTTP浏览器请求,并将数据返回到浏览器。 在子进程中调用此函数-每个Web请求一个。 它还允许主Web服务器进程继续等待更多连接。 进行检查以确保请求是安全的并且可以完成。 完成检查后,它将请求的静态文件传输到浏览器并退出。 main() 这是Web服务器的主要处理功能。 检查命令参数之后,它将为传入的浏览器请求创建一个套接字,位于接受请求的循环中,并启动子进程来处理它们。 它永远不会结束。

    伪代码

    下面的清单1是大约200行源代码的伪代码。 它应该可以帮助您了解程序的流程。

    清单1.伪代码
    logger() { outputs error, sorry or log messages to the nweb.log file if a sorry message, transmit it to the browser as a fake HTML response if error or sorry message the program is stopped } web() - this function returns the request back to the browser { read from the socket the HTTP request check it’s a simple GET command check no parent directory requested to escape the web servers home directory if no file name given assume index.html check the file extension is valid and supported check the file is readable by opening it transmit the HTTP header to the browser transmit the file contents to the browser if LINUX sleep for one second to ensure the data arrives at the browser stop } main() { if option is "-?", output the hints and stop check the directory supplied is sensible and not a security risk become a daemon process ignore child programs (to avoid zombies when child processes stop) create a socket, bind it to a port number and start listening to the socket forever { wait and accept incoming socket connection fork a child process if the child process then call the web function else close new connection } }

    套接字系统调用

    如果您以前从未遇到过这些网络套接字系统调用,则下面将对它们进行解释-尤其是它们如何组合在一起。 您也可以在手册中或在Web上查找它们-尽管可能很难从源代码和手册中看到它们的作用以及为什么它们构成Web服务器。

    socket() , bind() , listen()和accept()网络系统调用一起工作以创建服务器进程。 他们建立了一个套接字,准备将它们组合使用时可以通过网络进行通信。 套接字是:

    输入和输出流-例如常规管道和文件。 远程访问服务器或从服务器远程访问-通过网络使用时。 双向-在两端读写相同的套接字时。 常规的读取和写入功能-用于发送和接收数据。 流(无自然结构)-您必须确定协议。 对于HTTP-请求消息和响应消息以回车符(C代码中的CR或/ r)和换行符(C代码中的LF和/ n)结尾。 URL以空格字符终止,并通过关闭套接字突出显示所请求文件的末尾。 HTTP中有更复杂的替代方法和许多可选功能,但这是最简单的方法。

    下面的图1说明了它们如何组合在一起:

    图1。

    socket()函数创建套接字并返回文件描述符,该文件描述符可与任何使用文件描述符的函数一起使用,例如读取,写入和关闭。 这些参数告诉操作系统您需要哪种类型的套接字和通信。

    在socket()和bind()的参数上,有数十种排列和组合。 对于使用IP的常规通用套接字,程序中使用的参数非常典型。 以我的经验,其他更复杂的选择很少。

    bind()函数将特定的端口号附加到套接字。 客户端尝试与您的服务器联系时,它将使用IP地址(通常是通过使用DNS服务将主机名转换为IP地址找到的)和端口号。 端口号告诉操作系统您要在服务器上使用的服务。 / etc / services文件中列出了大多数UNIX计算机上通用端口号的详细信息。 这些文件包括用于服务的标准端口号,例如FTP(端口21),Telnet(端口23)和Web服务器(通常为端口80)。 您应该检查/ etc / services文件中的端口号,以确保不要尝试使用已经使用的端口号。 尽管,如果尝试这样做,您应该在日志文件中收到一条错误消息,因为两个服务通常无法使用相同的端口号。

    listen()函数调用告诉操作系统您现在准备接受传入的请求。 这是使套接字可用于本地和远程程序的最终开关。

    除非对此IP地址和端口号有套接字连接请求,否则accept()函数不会返回到您的程序。 accept()函数使用套接字的文件描述符,但是传入连接的新结构( struct sockaddr_in )允许服务器检查谁在连接谁。 在nweb中,您不在乎谁在连接。

    一旦accept()函数返回,则意味着客户端套接字文件描述符处于活动状态。 如果从套接字读取字节,则从客户端获取字符,如果将字节写入套接字,则它们将传输至客户端。 但是,从主程序读取或写入套接字并不常见。 通常,您希望允许多个客户端访问服务器的服务,并且如果此主程序执行读取或写入操作,则它可能会阻塞,直到有待读取的字符或传输的写入字符为止。 该主程序应该再次运行accept()函数,以允许新的连接开始。 主程序应启动一个子进程,该子进程与客户端进行“对话”及其所需执行的任何工作。 对于要关闭套接字的主程序,请重新运行accept函数并等待下一个客户端连接。 当子进程启动时,它将继承父进程打开的套接字,以保持套接字活动。

    其他系统调用

    getenv() 一个简单的函数,用于返回shell变量值。 如果设置Korn shell变量$ export ABC=123 ,则getenv(“ ABC”)函数将返回一个指向包含123的空终止字符串的指针。 chdir() 更改目录。 fork() 启动子进程。 在父级中,它返回子级的进程ID(PID),在子级中,它返回零。 setpgrp() 设置进程组。 这样做的结果是使该进程脱离了该用户启动的其他进程,因此不会受到用户所发生的事情(例如注销)的影响。 signal() 确定当软件中断到达该过程时会发生什么。 在nweb.c代码中,主服务器希望忽略子信号的死亡。 否则,主服务器进程将必须为每个子进程运行wait系统调用,否则它们将永远陷入“僵尸”状态,以等待父进程调用wait()函数。 最终,僵尸进程将太多,并且用户的环境将挂起,因为它们无法创建更多进程。 open(), read(), write()和close() 这些是常规的C库函数,但是当文件描述符访问文件和套接字时,它们用于读取发送到客户端Web浏览器的套接字和文件。 套接字使用accept()函数打开; 代码中的open()用于打开请求的文件。

    浏览器和Web服务器之间使用的协议

    有关该协议的完整详细信息,请参考万维网联盟(W3C)。

    浏览器发送:可以忽略GET URL和许多其他内容,但是某些浏览器或应用程序可能会检查它的浏览器类型,操作系统等。

    范例:GET /index.html

    示例:GET /mydirector/mypicture.jpg

    “ /”是指Web服务器支持的文件顶部的目录-这是在启动nweb时作为第二个参数指定的目录。 Web服务器永远不要在此目录之外发送任何文件。 某些Web服务器允许为特殊文件类型(例如CGI脚本)指定其他目录,但是nweb不处理此类特殊服务。

    Mozilla Firefox 10浏览器向nweb发出请求,如以下代码所示:

    GET /index.html HTTP/1.1**Host: 192.168.0.2:8181**User-Ag ent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:10.0.5) Gecko/20100101 Firefox/10.0. 5**Accept: image/png,image/*;q=0.8,*/*;q=0.5**Accept-Language: en-us,en;q=0.5**A ccept-Encoding: gzip, deflate**DNT: 1**Connection: keep-alive**Referer: http://w w.abcncc.uk.abc.com:8181/index.html**Cookie: UnicaNIODID=tVJWsYnAGTS-Xf5TqZJ; IB M_W3ABC_ACCESS=wwww3.abc.abc.com; IBMISP=70fdfc95d93011d783e4de784ea97766-70fdfc 95d93011d783e4de784ea97766-f67749a8b899e8ceed7e940b8c4bf189; returnURL=http%3A%2 F%2Fwww-933.abc.com%2Fsupport%2Ffixcentral%2Faix%2FdownloadOptions; PD-FROMPAGE= e%3D7.1%26function%3Drelease; PD-REFPAGE=/support/customercare/common/login%3Frt n%3Dfixcentral; PD-ERR=; PD-HOST=www-304.abc.com; PD-SGNPAGE=%2Fsupport%2Fcustom ercare%2Fcommon%2Flogin%3Frtn%3Dfixcentral; PD-REFERER=http%3A%2F%2Fwww-933.abc. com%2Fsupport%2Ffixcentral%2Faix%2FselectFixes%3Frelease%3D7.1%26function%3Drele ase; IBMISS=70fdfc95d93011d783e4de784ea97766-70fdfc95d93011d783e4de784ea97766-00 012ZSL0GV7c$TERIaw13er_zZ:15afkdghj-1340791897187-781004b2f37ec973cc8136eb5ddb53 67; rlang=en_US; ASESESSION2=EkSpHzOnrtcT90+EMdCXhRrFB3U+LxgKvOgc2ig+py0+Zq4GFgy UHQB35BGnKy4i3pb6pyO0DkVv+6S/RizqpusAst5sz+xESyBQv4dsfWVm

    总共大约是1300个字节。

    其中很多内容都在网络标准中,但对我而言基本上没有任何意义! 您需要阅读万维网联合会(W3C)中的所有详细信息。 只需要使用第一部分,即GET命令和文件名,其余信息可以忽略。 从HTTP / 1.1开始,Web服务器可以选择使用其他详细信息。 请注意,它正在发送信息,例如PC操作系统(此处为Microsoft®Windows®7)以及浏览器和版本(此处为Firefox 10.0.4)。 这是Web服务器如何报告正在使用该网站的用户的详细信息。

    nweb Web服务器检查是否:

    1.这是一个GET请求-仅支持GET或get 。

    2.文件名中没有“ ..”。 这意味着浏览器或用户正在尝试获取不在Web服务器目录中且不允许的文件。

    3.支持文件扩展名,例如.hmtl,.jpg,.gif等; 仅支持有限的文件类型。

    如果它是有效的GET请求,则nweb Web服务器将响应:

    HTTP/1.1 200 OK[Newline] Server: nweb/22.0[Newline] Content-Length: 12345 [Newline] Connection: close[Newline] Content-Type: text/html[Newline][Newline]

    关于此标头响应和返回文件的注释:

    C语言中的[换行符]为\ n ,表示发送了两个字符=回车符和换行符 。 这些换行符不是可选的。

    HTTP / 1.1 200 OK

    nweb不支持高级Web服务,因此它使浏览器知道它仅支持HTTP标准1.1(这是目前使用的最低标准),而200是一个特殊数字,用于通知浏览器Web服务器将响应。

    伺服器:nweb / 22.0

    这将通知浏览器它连接到的Web服务器的类型以及版本号。 在这种情况下,浏览器将不会做任何事情,因为它不会听说过nweb,但是浏览器可以告诉用户另一端是哪种类型的Web服务器,建立统计信息,开始使用Web服务器特定的功能或解决该问题。已知的Web服务器错误。 这取决于浏览器开发人员,因为至少他们知道套接字另一端的内容。

    内容长度:12345

    是即将发送的文件的确切长度(以字节为单位)。 在这个例子中,我只是编了一个数字。 字符数从[Newline] [Newline]开始

    连接方式:关闭

    这告诉浏览器,Web服务器不会为下一个请求保持连接打开状态,因此它需要为网页的其他项目建立另一个套接字连接。 通常,网页现在由基本页面内容以及数百种其他图形,Javascript等组成。 立即关闭链接不利于性能,但是会使nmon Web服务器代码变得简单。 否则,稍后我们将不得不开始超时以关闭套接字。

    内容类型:text / html

    这告诉浏览器有关它将作为响应获取的文件格式/内容。 nmon仅返回文本或图像文件,可接受的列表在扩展数据结构的C代码中。 第二部分是详细的文件类型。 在我们的例子中,基于简化文件的扩展名,我认为这个想法可能是文件根本没有扩展名,或者可能是错误的或毫无意义的。 因此,文件扩展名可能不会告诉浏览器文件格式。 并且,这为浏览器提供了有关如何处理内容的提示。

    接下来有两个换行符 ,这不是可选的。 它告诉浏览器,没有更多的标题信息行,并且文件内容紧随其后。

    然后, 文件内容被发送到浏览器。 由于第3行中两个换行符之后的长度,浏览器知道它何时完成。

    发送文件的最后一个字节后,nweb Web服务器的web()函数将停止一秒钟。 这是为了使文件内容可以通过套接字发送。 如果立即关闭套接字,则某些操作系统不会等待套接字完成发送数据,而是会突然断开连接。 这将意味着某些文件内容将无法到达浏览器,这将使浏览器永远等待文件的最后一部分,从而使浏览器感到困惑,并经常导致显示空白网页。

    最后,关闭套接字,子进程退出(停止)。

    客户端套接字系统调用

    nweb.c代码仅向您显示套接字的服务器端。 下面的清单2展示了客户端所需的代码。 幸运的是,客户端要简单得多,因为您只需提供IP地址和端口号即可建立与服务器的连接。 在下面的代码中,假定服务器的IP地址为192.168.0.42,服务器端口的假定为8181。实际上,在代码中硬编码这些数字不是一个好主意。

    清单2.客户端套接字系统调用
    int sockfd; static struct sockaddr_in serv_addr; if((sockfd = socket(AF_INET, SOCK_STREAM,0)) <0) pexit("Network socket system call"); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("196.168.0.42"); serv_addr.sin_port = htons(8181); if(connect(connectfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) <0){ perror("Network connect system call"); exit(3); /* now the sockfd can be used to communicate to the server */ /* with read() and write() */

    当心 : bind()和connect()函数采用结构作为参数,在设置特定选项之前必须将其填充为零。 在nweb.c中 ,将结构设为静态C变量,以确保在程序启动时将其填充为零。 如果此结构作为函数中或进程堆中的变量放在堆栈中,则可能会用旧数据填充它,因此其内容不会被零填充。 在这种情况下,您需要确保填充为零。 bzero()函数可用于执行此操作。

    服务器源代码

    有关服务器源代码,请参见清单4 。

    清单4 README.txt – 关于代码如何在实践中工作,编译,运行和测试nweb代码的许多提示和技巧

    提供这200行源代码作为示例工作示例。 这是所有基本的UNIX系统编程,大多数程序员都会想出类似的东西,因此不能将其作为原创作品进行版权保护。

    您可以自由地将其用于任何目的,没有任何限制,也没有给出或暗示的担保。 如果确实将它用于某些项目或产品,则建议指向该网页或对其的引用,但这是完全可选的。

    下载内容包括:

    nweb.c-源代码 client.c-示例客户端源代码 nweb可执行文件用于: 适用于POWER的AIX 6.1 TL7 适用于x86_64的Ubuntu 12.4 适用于x86_64的Fedora 17 Linux 适用于x86_64的OpenSUSE 12.1 Debian Squeeze for Raspberry Pi上的ARM 最低测试网站 index.html-测试网页 nigel.jpg-测试图像文件

    翻译自: https://www.ibm.com/developerworks/systems/library/es-nweb/index.html

    相关资源:NwebCrawler
    Processed: 0.107, SQL: 9