查看原文
其他

看问题要看到本质:从Web服务器说起

码农翻身刘欣 码农翻身 2021-04-20

这是个很长的故事, 让我们从Web服务器来开始。


Web服务器是个挺简单的东西,工作很简单,在80端口上监听,解析客户端发过来的HTTP的请求, 然后把相对应的HTML文件、Image等返回给客户端就可以了。 像这样:


这就是一个静态内容服务器,所谓静态内容,就是服务器端的内容如HTML不会变化,每次请求都是一样的。除非人们手工改了它。


实现这样一个“玩具Web服务器”并不难,只要能了解服务器端Socket编程就可以了, 主要工作是编程处理HTTP协议的细节。



动态内容



但是如果想再往前走一步,让Web服务器能产生动态内容,那就难了。


比如说来了一个HTTP请求,在其中携带者用户名和密码,要求你去数据库做一个查询,看看用户是否存在。


POST  /login

user=xxxx&pwd=xxx


这个静态的Web服务器就搞不定了,它根本,也不应该去查询数据库。


怎么办呢?你可以用某种语言(比如C语言)写个程序, 来查询数据库,假设这个程序的名字叫db-query。


可是你将面对非常棘手的问题:  Web服务器是个进程,db-query也是个进程,这俩货之间怎么通信呢?


(友情提示,下面内容略显枯燥,可跳过)


首先是参数的传递,一种办法是这样:对于每个动态请求,Web服务器进程创建一个db-query的子进程,然后通过环境变量把参数传递过去。


web服务器:

setenv("QUERY_STRING","user=xxxx&pwd=xxx")


db-query子进程 :

param = getenv("QUERY_STRING")。


下一个问题:db-query这个子进程获得了用户名和密码,查询了数据库,怎么把查询结果返回给浏览器?


有个很巧妙的办法!


每个程序都有所谓的标准输出(STDOUT),db-query只要调用printf这个函数,数据就会输出到STDOUT,我们就可以在黑乎乎的控制台上看到了数据输出了。


但是输出到控制台是万万不行的,我们得输出到socket才可以发回浏览器。


每个浏览器和服务器的连接都是一个Socket, 每个socket都有一个文件描述符fd, 如果把查询数据库程序db-query的STDOUT重定向到那个fd,会发生什么?


没错!db-query的所有输出都直接发送的客户端的socket了,Web服务器可以撒手不管了!


当然,如果浏览器要看到的是HTML页面, 那db-query这个程序就需要输出HTML了。


这种方式就就是大名鼎鼎的CGI,当你看到网址中有cgi-bin字样的时候,很有可能就是用CGI实现的。  只要遵循CGI协议, 可以用任何语言来实现动态的网站。


这是人类迈出的一大步,有了这一步,才能在网上购物,办公,社交,聊天......  你才能看到我这篇文章(嗯,也许腾讯把微信公众号的文章都静态化了, 请了解详情的同学告知)


但是,CGI是非常复杂和笨拙的, 主要体现在:


第一,对每个请求,都得创建一个子进程去执行,这是个非常大的开销。


第二,对程序员来说,编程极为痛苦,要操作环境变量,还需要直接在编程语言中输出HTML!


麻烦不麻烦,难受不难受,上个世纪的程序员苦逼不苦逼?



Servlet



怎么才能跳出苦海?必须得做到关注点的分离!


程序员的关注点是:拿到Http 请求中的数据,执行业务, 然后输出Http 响应。 别的什么环境变量,重定向,别来烦我!


那就简单了,让程序员写个类,里边是业务逻辑, 然后我们想办法构建一个HttpRequest对象和HttpResponse对象,传递给程序员的类让他使用不就行了?


谁来创建这个HttpRequest和Response 对象,  然后调用程序员写的类?


静态Web服务器表示我不愿意,我就想管好我这一亩三分地,把静态内容给大家服务好。


Tomcat已经迫不及待地要上场了,我来我来。码农朋友们,我送给你们一个规范,叫Servlet, 你们按照Servlet的规范来写程序,放到我这里运行,别的什么都不用管了。


程序员很高兴,只需要写简单的Servlet就行了,HttpRequest和HttpResponse对象由Tomcat来创建,可以从HttpRequest中获得Header, Cookie, QueryString 等信息, 从HttpResponse中获得输出流,直接向浏览器输出结果, 简单又直接。


Tomcat还郑重向大家声明:对于每个请求,我只会用一个线程来出来,线程的开销可比进程小多了。


对于那个在代码中混杂HTML的问题怎么处理?


 Tomcat也有办法, 可以在HTML混杂代码!这就是JSP。执行期其实会被编译成Servlet。


(码农翻身注:请移步《JSP:一个装配工的没落》)


你看,责任分离了,每个人只要办好自己的事情就好。


(注:实际上,我们不会在Servlet中写业务逻辑, Servlet现在通常是一个通往框架的入口。)


WSGI


CGI表示不服:遵循我的协议,任何语言都可以来实现动态网站,你Servlet只是Java规范,不管别的语言了?


Servlet规范确实没法跨语言实现,那要是Python也想做动态Web网站,该怎么办?


既然已经认识到动态网站的本质了, 可以采用类似的思想来处理嘛! 我们为Python也定义一个规范,叫做WSGI (Web Service Gateway Interface)。


让程序员写个类或者函数(称为wsgi application),在其中实现逻辑。让某个动态服务器(称为wsgi server)把Http Request和Response传递给它,就可以执行了。


但是Python表示:我不喜欢你们Java 那一套啰里啰嗦的类,HttpRequest 不就是一些key value吗?放到我钟爱的dict中多好 !我把它叫做enviroment, HttpResponse也没必要,直接用函数的返回值(确切说是一个可迭代对象)就好。


看看,是不是和Java 的Servlet 很像?(当然,忽略了很多细节。)


从本质上来说,都是为了关注点的分离:


1. 用一个动态内容服务器(wsgi server,Tomcat等)来接受并且封装HTTP 请求,降低程序员的负担。


2. 程序员只需要遵循约定(servlet,wsgi)就可以轻松实现自己的业务,不用关注系统的处理细节。


如果你先学的Java,通过Servlet理解了动态内容网站的本质和解决问题思路,再看到Python的wsgi,一眼就能看透,学起来飞快,反过来也是如此。


Web服务器的例子还比较简单,但是也体现出了这个道理:遇到问题要深度思考,努力看到本质,这样才能举一反三。



码农翻身公众号开放投稿,可能是全网最高片酬:

用故事讲技术 ,稿费1000

技术/职场/感悟/面试等,稿费700

翻译类文章,每千字200

详情猛戳: 可能是全网最高片酬,速来!


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存