httpd和tomcat组成的基本web服务框架(cgi、php、servlet、jsp)

本文详细介绍了如何使用httpd和tomcat搭建基本的Web服务框架,涵盖了httpd处理静态和动态请求(包括CGI、PHP),以及tomcat的部署和处理动态请求(Servlet、JSP)。通过实例展示了如何编写和运行CGI程序(C、bash、perl、python),PHP脚本,以及创建和配置servlet与jsp文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

框架图

一般情况下的基本web服务框架:

  • httpd(即apache)直接给用户提供web访问服务,它自身可以处理静态请求(如html页面),也可以处理php及cgi这两类动态请求
  • tomcat不直接面向用户,一般在httpd的配置文件中增加配置将某类请求转发至tomcat,特别是httpd无法处理的jsp动态请求
    在这里插入图片描述

httpd

httpd的部署

本机通过yum安装httpd服务,自动安装在目录/etc/httpd/下,利用systemctl进行服务控制,服务名字叫做httpd。
配置文件是conf/httpd.conf,修改处:

Listen 801;

是的,我就改了一下端口,因为本机还有其他无关的服务(和本文无关)监听80端口。然后启动服务。
记住:httpd所有响应都来自于/var/www/下,无论是静态响应还是动态响应。

httpd处理静态请求

静态请求即用户请求一个静态文件,该文件来自于磁盘。httpd的静态文件放在目录/var/www/html/下,如该目录下有一个test_static.html文件,内容是hello ,this is httpd
以下是浏览器测试:
在这里插入图片描述

httpd处理动态请求

动态请求即请求到来的时候服务需要执行代码产生内容,然后响应给用户,该响应来自于CPU计算。

cgi模块

所谓的cgi,全称是Common Gateway Interface(通用网关接口),它是一种标准,符合该标准的程序叫做cgi程序。看名字看不懂,它实际上就是httpd进程拉起另外一个进程,由另外一进程来接手处理该请求,步骤如下:

  • httpd进程的cgi模块fork一个子进程
  • 子进程调用setenv将请求头及其它请求相关的信息写入到环境变量中,环境变量名为:QUERY_STRING、SERVER_PORT、REQUEST_METHOD、REMOTE_ADDR、REMOTE_HOST、CONTENT_TYPE、CONTENT_LENGTH等
  • 子进程调用dup2将标准输入重定向到和客户端相关联的已连接的描述符,然后就可以从标准输入中读取请求体
  • 子进程调用dup2将标准输出重定向到和客户端相关联的已连接的描述符,然后它写到标准输出的东西都会直接到达客户端(状态行以及大部分响应头已由父进程httpd产生,响应体以及响应体属性相关的响应头由子进程产生)
  • 子进程调用execve在子进程的上文中执行请求所要求的程序(程序名在uri中指定)
  • 父进程(httpd进程)阻塞在对wait的调用中,等待子进程终止的时候回收操作系统分配给子进程的资源

cgi程序都在/var/www/cgi-bin/下。cgi程序包括可执行的二进制文件或者脚本文件。

二进制

二进制一般用编译型语言(比如C和C++)编写,然后编译链接成一个可执行文件。
现在用C语言编写一个文件,然后编译链接成可执行文件并增加可执行权限,文件名是test_c_cgi,其对应的C源代码是:

#include<stdio.h>
int main()
   {
       printf("Content-Type:text/html\n\n");//响应头
       printf("Hello, this is cgi written in C");//响应体
       return 0;
   }

以下是浏览器测试:
在这里插入图片描述

bash脚本

现在用bash脚本语言编写一个文件,然后增加可执行权限,文件名是test_bash_cgi,其内容是:

#!/bin/bash
echo "Content-Type:text/html"
echo ""
echo "hello, this is cgi written in bash"

以下是浏览器测试:
在这里插入图片描述

perl脚本

现在用perl脚本语言编写一个文件,然后增加可执行权限,文件名是test_perl_cgi,其内容是:

#!/usr/local/bin/perl
print "Content-Type:text/html","\n","\n";
print "hello, this is cgi written in perl";

以下是浏览器测试:
在这里插入图片描述

python脚本

现在用python脚本语言编写一个文件,然后增加可执行权限,文件名是test_python_cgi,其内容是:

#!/usr/bin/python
print("Content-Type:text/html")
print("")
print("hello, this is cgi written in python")

以下是浏览器测试:
在这里插入图片描述

php引擎

php文件和静态文件一样,都在目录/var/www/html/下。
什么是php文件?通俗说就是在一个静态html文件中嵌入php代码,当用户请求这个文件(uri中指定)的时候,httpd的php引擎先执行文件中的php代码,然后用执行后的结果替代掉代码,最后将整个文件内容响应给用户。
现在用php脚本语言在一个有一些静态html内容的文件中插入一段代码,代码的功能是根据请求参数中的age的值来计算明年用户是多少岁(这是一个很吊的逻辑),文件名是test_php.php,文件内容是:

<!DOCTYPE html> 
<html> 
<body> 

<?php//php代码开始
  $age=($_GET["age"]);
  echo "你的年龄是: ", $age, ", "; 
  echo "明年你的岁数是: ", ($age+1);
?>//php代码结束

<p>hello, this is a php file, this content is same for every request</p>//这是html文件中写死的一个段落
</body> 
</html>

以下是浏览器测试:
在这里插入图片描述

tomcat

tomcat的部署

本机直接到tomcat官网下载二进制版本apache-tomcat-8.5.85.tar.gz,选择解压在目录/root/apache-tomcat-8.5.85/下,以下所有提到的相对路径都是现对于这个目录。
启动服务命令是:/root/apache-tomcat-8.5.85/bin/startup.sh
停止服务命令是:/root/apache-tomcat-8.5.85/bin/shutdown.sh
配置文件是conf/server.xml,修改处:

<Connector port="8001" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

此处我也只是改了一下端口,把它默认监听的8080端口改成8001,然后启动服务。

tomcat处理动态请求

tomcat是可以单独对外提供web服务的,但是一般是隐藏在httpd后面,在httpd的配置文件中配置一个转发指令来将某些请求转发到tomcat来处理,我的做法是:在/etc/httpd/conf/httpd.conf中加入这样一条指令:

ProxyPass /tomcat/  http://127.0.0.1:8001/

该指令的意思是:所有以/tomcat/开始的uri的请求都转发到本机的8001端口,该端口正是tomcat在监听。

servlet引擎

什么叫做servlet?简单来说,一个servlet就是一个java类。先按照特殊规范写一个能够处理请求的java类,然后编译成java字节码(从.java文件到.class文件),当请求经路由后被指定由这个java类来处理的时候,tomcat中的servlet引擎将解释执行相应的java字节码来处理请求。
所有编译后的servlet都放在目录webapps/ROOT/WEB-INF/classes/下。
上述介绍过的处理动态请求的方法都没有涉及数据库,servlet举例将涉及到数据库查询以免让你觉得我很菜。

  • 创建servlet。现在有一个servlet叫作TestMySQL,它是一个java类,类名就叫TestMySQL,定义在文件TestMySQL.java中。该类按照规范有一个doGet方法(该方法是固定的名字不是随便取的)来处理GET请求,该方法的逻辑是根据uri参数中的name参数值到数据库pzhtest的表haha中查询该name对应的age是多少,然后响应给用户。文件内容如下:
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestMySQL extends HttpServlet {
    //JDBC驱动名及数据库URL
    static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";  
    static final String DB_URL = "jdbc:mysql://localhost:3306/pzhtest";
    
    //数据库的用户名与密码,需要根据自己的设置
    static final String USER = "root";
    static final String PASS = "****";//这密码是我写文章的时候故意设置成星号,能让你看到? 

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Connection conn = null;
        Statement stmt = null;
        //设置响应内容类型
        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();
        String docType = "<!DOCTYPE html>\n";
        out.println("<!DOCTYPE html>\n" +
        "<html>\n" +
        "<body>\n");
        try{
            //注册JDBC驱动器
            Class.forName("com.mysql.jdbc.Driver");
            
            //组装SQL查询语句
            String query_name = new String(request.getParameter("name").getBytes("ISO8859-1"),"UTF-8");
            String sql = "SELECT age FROM haha where name='" + query_name + "'";

            //连接并执行查询
            conn = DriverManager.getConnection(DB_URL,USER,PASS);
            stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(sql);

            //展开结果集
            while(rs.next()){
                //通过字段检索
                int age = rs.getInt("age");
    
                //输出数据
                out.println("age of " + query_name);
                out.println(" is : " + age);
                out.println("<br />");
            }
            out.println("</body></html>");

            //完成后关闭
            rs.close();
            stmt.close();
            conn.close();
        } 
       
    }

}
  • 编译servlet。在servlet的编写规范中,以为要处理http请求所以每个servlet都要导入javax.servlet,如果需要和数据库交互,还要导入java.sql,这两个jar包要提前下载到目录lib/下,编译的时候指定好(用-cp选项指定)。该servlet已经写好,现在要编译它,编译语句为:
javac -cp /root/apache-tomcat-8.5.85/lib/mysql-connector-java-5.1.39-bin.jar -cp /root/apache-tomcat-8.5.85/lib/servlet-api.jar TestMySQL.java

编译后得到了文件TestMySQL.class,它的内容就是java字节码,可以由servlet引擎解释执行。

  • 路由。那么如果将请求路由到该servlet呢?打开文件webapps/ROOT/WEB-INF/web.xml,在标签之前加入以下条目:
  <servlet>
        <servlet-name>aaaaaa</servlet-name><!--此处的servle-name可以随便取名-->
        <servlet-class>TestMySQL</servlet-class>
  </servlet>

  <servlet-mapping>
        <servlet-name>aaaaaa</servlet-name>
        <url-pattern>/query_age</url-pattern>
    </servlet-mapping> 

配置的意思是:uri为/query_age的时候,将请求路由给TestMySQL这个servlet处理。
以上就阐述完了servlet的创建、编译和路由。
假设数据库中name为pzh的人的age是30(不用猜,我就是pzh,我今年30岁),下面是浏览器测试:

jsp引擎

jsp本质上和php本质类似,就是在一个静态html文件中嵌入动态代码,不同的是语法和引擎执行过程有区别。
jsp文件是用java写的,那它和同是java写的servlet有啥区别?我们细看servlet发现:它需要在代码里大量调用out.println打印出所有的html页面内容,而jsp文件中大部分html内容都写死,只在部分需要动态产生的内容处加入代码逻辑。
jsp引擎做的事情超级容易理解,它接手请求后做的事情:

  • 加载相应jsp文件
  • 把jsp文件内容转化为一个java类:其中写死的html内容改用out.println来代替(就这?),再加上其他jsp代码一起转化为一个servlet
  • 编译该servlet
  • 将请求转给servlet引擎执行该sevlet

这么看,jsp引擎就是一个皮包公司。php引擎和它同为兄弟,php就要事事亲力亲为了。但是毫无疑问,jsp文件的编写比自己写一个不出错的servlet更容易(所以到处取代码农的事情到处都在发生)
jsp文件都在目录webapps/ROOT/下。
现在有一个jsp文件,文件名是test_jsp.jsp,内容是:

<html>
<body>

<%//jsp代码开始
out.println("hey, brother, you address is " + request.getRemoteAddr());
%>//jsp代码结束

</body>
</html>

浏览器测试:
在这里插入图片描述

总结

以上对该服务框架进行全面但浅显的介绍。本文一定有不严谨处可供专业看官喷一喷,但是我的目的就是通识了解,我也是因为工作中恰好需要具备对它的通识了解才专门实践、证实并编写的本文。

因为本人的工作内容是用nginx+lua做web网关开发。web网关一个重要功能模块是waf检查,不少代码逻辑和大量的waf规则都是和httpd/tomcat有关,所以我就写了本文来记录一下,防止自己忘了又去乱查。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值