# Web的历史2️⃣-动态网页 上篇文章我们已经了解了静态网页是如何工作的,但是这样的网页是不能满足大家对互联网的需求的。举例子来说:你访问b站首页`bilibili.com`,每次刷新,首页上显示给你的视频都不一样,不同的人访问这个首页,显示的也不一样,按理说大家都是访问一个网址,背后应该都是同一个文件,为什么每个人都不一样呢?这种功能是如何实现的? 淘宝上有数不清的商品在售卖,如果淘宝为每一个商品都在服务器目录下面创建一个html文件,好让大家通过访问`http://taobao.com/someproduct.html`来查看商品信息,那这个工作量就非常大了。而且,这样的网页,基本上没有交互的功能:我们希望用户可以点击按钮就能购买商品,商家在网页后台上操作就能上传商品。这种功能应该如何实现呢? 暂时先不考虑这些高级的问题,让我们先从最基础的讲起: ## 服务器端内嵌(SSI) 如果你想向网站中插入动态内容,SSI是最简单,最直接的办法,比如我们的wiki有许多页面,但是每个页面都有一些共同的元素:页面头部的导航栏,左侧的列表,页脚等。如果为每个页面都复制一份相同的HTML的话,那就太麻烦了,有没有什么办法,可以使HTML一次编写,到处渲染呢? SSI(Server Side Includes)就是满足这种需求的一个HTML宏语言。它有点类似于C语言的`# include`宏: 假设这是我们首页的HTML: ```html

wiki

``` 假如`navbar.html`的内容如下: ```html 教程 文档 高级 Github ``` 那么用户访问我们首页时就会看到: ```html

wiki

// highlight-start 教程 文档 高级 Github // highlight-end
``` 如果导航栏的界面有变化,那么只需要修改`navbar.html`即可,不用修改网站中的每一个页面。 没错,SSI的功能就是简单地把制定的内容插入进HTML里。这对一些重复的元素(例如每个网页的页头,页脚,侧边栏)还有一些需要更新的内容很实用。 当然,SSI并没有解决动态网页的问题,它只是把需要手动更新的地方单独拿了出来,使维护静态网站更容易,所以程序员们又发明了CGI技术。 ## CGI **CGI(Common Gateway Interface)** 是第一个真正实现动态网页的技术,它允许Web服务器执行外部程序来生成网页内容。 CGI的工作原理是:当用户访问特定URL时,服务器不是返回静态文件,而是执行一个程序,并将程序的输出作为HTTP响应返回给用户。 (举个天气预报的例子): Web服务器通常会把能执行的程序(除开静态文件)放在一个叫cgi-bin的特殊目录里。假设我们服务器的这个文件夹里有一个查询天气的Python程序`weather.py`,当用户访问`http://example.org/cgi-bin/weather.py?city=中山&date=2025-06-25`时,我们的HTTP服务程序会自动**执行**放在路径中`/cgi-bin/weather.py`的这个Python脚本,并且将客户端的请求头和请求体传递给脚本; 脚本解析请求头中`city=中山&date=2025-06-25`这个参数,在数据库中查询这个日期的天气,然后返回一个HTML给HTTP服务程序,再把这个HTML返回给客户端。 如果没有设置CGI,那么服务程序只会返回给客户端`TodayWeather.py`这个脚本文件的代码本身。 其实,CGI是一个接口格式,它定义了我们编写程序与HTTP服务程序之间如何交互。通常,HTTP服务程序给CGI程序的输入就是环境变量,输出就是标准输出。 CGI的巧妙之处在于,服务器不是用什么复杂的方式和脚本沟通,而是把请求信息(比如URL参数里的城市)变成程序很轻松就能读到的环境变量。而程序也不需要复杂的操作进行IO,它只需要把生成的HTML代码打印出来,服务器就会自动收集这些打印的内容,然后发回给用户的浏览器。 下面是一个例子: ```bash title="/var/www/cgi-bin/system-info.sh" #!/bin/bash # HTTP响应头 echo "Content-type: text/html" echo "" # HTML内容 echo '' echo '系统信息' echo '' echo '

服务器系统信息

' echo '

当前时间:'$(date)'

' echo '

内存使用情况:

' echo '
'
free -h
echo '
' echo '' echo '' ``` 每次用户访问这个页面,都会看到实时的系统信息,真正实现了动态内容。 虽然CGI现在很少见了,但它建立了一个重要概念:将URL请求映射到程序函数,而不是静态文件。这个思想成为了现代Web开发的基础。 ## 嵌入式脚本 随着动态网页需求的增长,纯CGI编程变得复杂。程序员们希望能够在HTML中直接编程动态代码,这样既保持了HTML的可读性,又能实现动态功能。 这个就是嵌入式脚本,顾名思义就是把脚本和HTML混在一起,在HTML中嵌入脚本; 但是这种脚本和今天的前端JavaScript不同,它是由后端解释执行的,在返回HTML响应之前,HTTP服务程序会检查这个HTML里面有没有可以执行的脚本内容,有的话就执行这些脚本,并且把脚本的输出嵌入到HTML里面。任何有效的HTML也是有效的这类脚本语言。 从CGI到嵌入式脚本的另外一个关键驱动力是性能。CGI每来一个请求,服务器就得创建一个新进程去运行CGI程序,完成后再销毁,开销很大。而嵌入式脚本通常则是直接作为服务器的一部分运行,效率远高于CGI。 ### JSP 举个例子吧,你可以轻松使用Java来创建动态网页,只需要把Java代码嵌入到HTML里面,使用`<% %>`包裹住代码: ```java

当前时间:<%= new java.util.Date() %>

``` 复杂一点的例子: ```java

欢迎访问我们的网站

当前服务器时间:<%= new java.util.Date() %>

您是第 <%= session.getAttribute("visitCount") %> 位访客

<%-- 这是JSP注释,不会出现在最终HTML中 --%> <% // 这里可以写复杂的Java逻辑 String userName = request.getParameter("user"); if (userName != null) { out.println("

欢迎您," + userName + "!

"); } %> ``` :::info[session和cookie] 在这段JSP代码中有一个对象叫做`session`,这是什么呢?实际上,因为HTTP是无状态的协议,意味着两次请求之间是完全独立的,一次请求不应该依赖另一次请求。这显得有点不灵活,于是人们会在HTTP的请求体上夹带一些额外的参数,用于表明用户的身份信息,比如在用户登录网站之后,服务器会给客户端一个密钥,下一次客户端请求页面时带上这个密钥,服务器就知道这是某个用户的请求。在这种模式下,服务器需要为每个用户维护信息,比如最简单地需要维护密钥是对应哪个用户的,这些信息就叫做session。 ::: 类似于这样的脚本叫做JSP(JavaServer Pages),它在后端返回时被转换成Java Servlet代码来执行,本质上,JSP是Java Servlet的一种语法糖。至于JSP和Java Servlet都是什么,自行了解吧。 ### PHP 比JSP更灵活的就是PHP,PHP就是一门纯正的脚本语言了,它的用法与JSP类似,使用`包裹代码`: ```php

欢迎来到我的网站

当前时间:$time

"; ?> ``` 也可以这样写,这样就类似于CGI程序的写法了: ```php "; echo "

欢迎来到我的网站

"; $time = date('Y-m-d H:i:s'); echo "

当前时间:$time

"; echo ""; ?> ``` ### LAMP 这种动态网页的编写方法流行了很多年,形成了一个叫做"LAMP"的套路:Linux+Apache+MySQL+PHP;就是将电脑装上Linux系统,运行Apache这个HTTP服务端,使用PHP作为动态脚本语言,使用MySQL来存储和访问业务数据。 需要注意的是,这四个都是开源免费的软件,LAMP的兴起,是开源软件运动的标志之一。开源软件使得部署网站的成本极大地降低,推动了互联网的繁荣。如果你想建站,那时候互联网上到处都是"LAMP一键安装脚本"之类的东西,现在也能搜到不少。一个下午就能上线一个完备的网站。这些技术的出现,使得开网站不再局限于大企业才能办得到的事情,一时间互联网上到处都是个人或者小单位的网站,甚至后来出现了诸如Wordpress之类的方案,不会写代码也能开网站。繁荣的生态,网页上丰富的动态内容,形成了被我们称为“Web 2.0”的时代。 LAMP的一个典型反面是微软全家桶:Windows Server+IIS+SQL Server+ASP,这套技术方案需要给微软缴纳高额的授权费用,在当时基本上只限于追求稳定和售后服务的企业使用。我们的文章也没有怎么介绍这些技术。不过IIS对于个人用自己的电脑建站还是非常方便的。(当然国内没有公网IP那是另一回事了╮( ̄▽ ̄)╭) ## MVC架构 随着网页的不断发展,出现了复杂的业务逻辑,并且页面也越来越复杂;这时候,把页面和程序逻辑混在一起的嵌入式脚本在庞大的复杂代码情况下变得难以维护。 而且它们都有一个特点:依赖于具体的某个HTTP服务程序,PHP依赖于Apache的`mod_php`或Nginx的FastCGI支持,JSP依赖于Servlet容器例如Tomcat,这增加了开发与部署的耦合度,更使得项目难以管理。嵌入式脚本难以复用已有的代码,这些代码的测试也需要模拟HTTP环境,难以测试。 此时兴起了一种新的Web后端编程思想,它就是MVC(Model-View-Controller) 简单来说,根据大量的开发经验累积,人们发现一个动态网页的后端通常需要做到这3件事情: - Model:使用面向对象的方法为业务建模,把数据对应到编程语言中的对象,把对数据的操作对应到对象的方法。负责对业务数据进行实际的操作。 - View:输入数据,负责把数据变成用户可以直观看懂的HTML。 - Controller:负责协调,调用上面两个部分。 ![MVC](/img/blog/model-view-controller-light-blue.png) 例如,当我们在报修系统中想要查询一个片区的全部报修时,首先我们访问`http://wwbx.zsxyww.com/QueryTickets.php?zone=朝晖&status=pending` 然后服务器根目录下的`QueryTickets.php`程序就会接受到我们的请求(在MVC时期的PHP程序已经不像嵌入式脚本那时混写HTML和PHP,整个文件就是以`