title: HTTP, Servlet, Tomcat
author: lensfrex
cover: https://oss-img.ciduid.top/blog/covers/topimage-xb3-pc.jpg
date: 2023-05-13 15:50:00
---
# 学习Servlet和Tomcat

## 前置任务：了解学习HTTP

如果你选了网络工程导论这门课，并且已经了解了HTTP相关的知识，则可以跳过本节，直接开主线任务：[传送点](#主线任务：Servlet，Tomcat)，但是也可以重新开始学习这部分的内容。

想了解更多：[HTTP | MDN](https://developer.mozilla.org/zh-CN/docs/Web/HTTP)

### HTTP介绍

超文本传输协议（HyperText Transfer Protocol，HTTP）是一个应用层协议。所谓协议，就是数据传输格式的一个约定。

HTTP最初设计是用来传输html页面这种纯文本数据的，但是实际上只要声明好header，传输什么都是可以的，图片，文件等等，或者也可以摇身一变，变成另一个协议(grpc, 基于http2)来使用，总之，http其实是非常灵活的。

###  HTTP消息

一个规范的，完整的HTTP消息结构是这样的：

请求消息(GET方法)：
地址：`https://github.com`

```http
GET / HTTP/1.1
Host: github.com
authority: github.com
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35
content-length: 0


```

请求消息(POST方法，带请求体)：

地址：`http://bkjx.wust.edu.cn/Logon.do?method=logon`

```http
POST /Logon.do?method=logon HTTP/1.1
Host: bkjx.wust.edu.cn
Proxy-Connection: Keep-Alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cache-Control: no-cache
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Cookie: bzb_njw=303511D07EF501147333F6B099D16CB9; SERVERID=122
Origin: http://bkjx.wust.edu.cn
Pragma: no-cache
Referer: http://bkjx.wust.edu.cn/
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35
Content-Length: 94

userAccount=&userPassWorld=&encoded=dMcs2P8fNag0n1d03f0UVgu02^%^257F^%^25QF^%^25pcs0Ygd4fbgHs69
```


响应消息(header内容精简过)：

```http
HTTP/1.1 200 OK
Server: GitHub.com
Date: Sat, 13 May 2023 02:59:34 GMT
Content-Type: text/html; charset=utf-8
Vary: X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Language, Accept-Encoding, Accept, X-Requested-With
content-language: en-US
ETag: W/"bfeda72459b363617514a4f13882cece"
Set-Cookie: _octo=GH1.1.496438640.1683116779; Path=/; Domain=github.com; Expires=Mon, 13 May 2024 02:59:39 GMT; Secure; SameSite=Lax
Accept-Ranges: bytes
Transfer-Encoding: chunked

<!DOCTYPE html>
<html lang="en"  data-a11y-animated-images="system">
  <head>
    <meta charset="utf-8">
  <link rel="dns-prefetch" href="https://github.githubassets.com">
...(还有一堆的网页内容)
```

请求和响应的消息体长得还挺像的，估计大家都能看出来了。

#### 第一行 -『起始行』与『状态行』

http请求消息中，第一行为『起始行』：
- `GET / HTTP/1.1`
- `GET /background.png HTTP/1.0`
- `POST /Logon.do?method=logon HTTP/1.1`

起始行分为三个部分，分别定义了请求方法，请求目标和当前请求使用的http协议版本

第一个部分为『[请求方法](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods)』，一般有GET, POST, OPTIONS, PUT等好多种，但是实际使用几乎就只有GET和POST两种。

随后紧接着的部分是『请求目标』，在普通的http请求中也可以简单理解为请求路径，也就是咱们看到的浏览器地址后面的部分(xxx.com/abcd/efg中的/abcd/efg，bkjx.wust.edu.cn/Logon.do?method=logon中的/Logon.do?method=logon部分) 

在部分场合下，请求目标有时也是一个绝对路径的URL，如当使用HTTP代理访问网站时，浏览器向代理服务器（如软件）发送的数据也是一个http请求，只不过真实的请求放在了body部分，代理程序只负责原样发送数据：`CONNECT google.com:443 HTTP/1.1`

这里就抄一下MDN的介绍：

> - 一个绝对路径，末尾跟上一个 '?' 和查询字符串。这是最常见的形式，称为原始形式（origin form），被 GET、POST、HEAD 和 OPTIONS 方法所使用。
> - - POST / HTTP/1.1
> - - GET /background.png HTTP/1.0
> - - HEAD /test.html?query=alibaba HTTP/1.1
> - - OPTIONS /anypage.html HTTP/1.0
> - 一个完整的 URL，被称为绝对形式（absolute form），主要在使用 GET 方法连接到代理时使用。GET http://developer.mozilla.org/en-US/docs/Web/HTTP/Messages HTTP/1.1
> - 由域名和可选端口（以 ':' 为前缀）组成的 URL 的 authority 部分，称为 authority form。仅在使用 CONNECT 建立 HTTP 隧道时才使用。CONNECT developer.mozilla.org:80 HTTP/1.1
> - 星号形式（asterisk form），一个简单的星号（'*'），配合 OPTIONS 方法使用，代表整个服务器。OPTIONS * HTTP/1.1

最后的一部分为『HTTP版本』，声明了这次请求使用的http版本。目前常用的版本有`HTTP/1.1`，`HTTP/2`，`HTTP/3`。

> 另外，和其他HTTP版本不同的是，HTTP/3版本是在基于UDP的QUIC协议之上实现传输的，QUIC是与TCP和UDP等同级（传输层）的协议，而其他的HTTP版本都是在TCP协议上实现的，

对于响应消息，第一行为『状态行』。
- `HTTP/1.1 200 OK`
- `HTTP/1.1 404 Not Found`
- `HTTP/1.1 500 Internal Server Error` **（心 脏 骤 停）**

也是三个部分。

响应的状态行就挺简单的了。

第一个就不用说了。

第二个则为『状态码』，就是咱们常见的404, 200这些，表明了服务端处理请求的结果状态。

第三个部分则为『状态文本』，其实就是状态码的描述。

有哪些状态码，可以[自行了解了解](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status)，最好记得一些常用的状态码的含义，要是乱抛状态码给前端有的是一顿毒打（

当然，在许多场合中，业务状态（成功，认证错误，参数错误等等）一般并不通过状态码表示，而是200响应，在响应消息体里边体现错误；但是也有状态码和消息体都体现的情况。具体还是要跟前端和客户端的小伙伴商量约定好规范和文档，按照团队的规范和文档行事，能少很多不必要的麻烦。

#### 『标头』（Header）

请求和响应的消息体过了第一行之后，就到了『标头』(header)部分了，一般咱们把这玩意叫『请求头』和『响应头』。

http消息的header实际上就是一个键值对，格式为`Key: Value`，一行一个

需要注意的是，这里的key是不分大小写的，例如`Host`，`host`，`HOST`，`hOST`都是同一个东西。

同一个key的Header也可以有多个值，因此，在各种http库中，获取到的header值都是一个List<String>。

比如：
```
Set-Cookie: logged_in=no; Path=/; Domain=github.com; Expires=Mon, 13 May 2024 02:59:39 GMT; HttpOnly; Secure; SameSite=Lax
Set-Cookie: access_token=abcdefg; Path=/; Domain=github.com; Expires=Mon, 13 May 2024 02:59:39 GMT; Secure; SameSite=Lax
```

虽然如此，但是除非有必要(例如上面的例子中给客户端返回Cookie)，在响应或者请求的时候最好不要这么干，即使可以这么干，否则在对方处理的时候会很麻烦。

一般来说，咱们会经常见到这些请求头或者响应头：
> - `Host`: 请求的目标主机，通常是在同一个ip和端口上有多个不同的服务时，提供给nginx或者apache等类似的其他的反代网关判断究竟应该执行哪个配置块时使用的。
> - `User-Agent`: 表明请求客户端信息，一般格式为`程序名/版本`，当然，也可以添加其他额外的信息，如firefox的ua：`Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0`
> - `Content-Type`: 请求/响应传输数据的类型，即下一节中body部分数据的格式。按照规范，这个字段的值应该使用MIME type格式，这里是一些[常用的MIME类型](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types)。
> - `Accept`: 能接受的数据格式，一般为用逗号`,`分隔开的若干个MIME type格式
> - `Accept-Encoding`: 指定能够接受的响应数据编码，和`Accept`不同的是，`Accept`指定的是body部分的数据格式，但是这里指定的是整个响应的数据编码，通常是压缩算法，如`gzip, deflate, br`，设置了这个值后，如果服务端支持，响应的时候会对整个HTTP消息进行相应的压缩后，再返回响应给客户端，客户端需要进行解压解码后再进行处理。一般http框架和web框架都能自动进行自动的处理。
> - `Referer`: 说明请求是从哪里来的
> - `Cookie`: 向服务器请求时附带的一些信息，就是咱们常说的cookie，浏览器在请求的时候如果有相对应的Cookie，会自动附带上这些Cookie进行请求。
> - `Set-Cookie`: 服务端响应回来的Cookie，浏览器看到这个header之后就会根据这个header的值去保存cookie到浏览器里边。下次请求cookie对应规则的地址时会自动附带这些信息，即上一条中的Cookie字段。

Cookie一般用于用户的识别和身份认证以及一些额外信息的保存。[了解更多关于Cookie的东西](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies)

实际上，很多header值只是作为一个参考，作为后端开发，不应该过于信赖前端/客户端发来的数据，后端程序必须要对前端/客户端做充足的数据校验，如`Content-Type`，前端/客户端可以在请求的时候在`Content-Type`字段里说他发了`image/png`图片，但是可能实际上发来的数据是一个程序，如果不做任何校验直接保存到服务器上，那可就麻烦大了。

一般来说，很多http请求和web服务框架都能帮咱们写好这些通用的请求头和响应头，不用咱们过于操心，当然，如果有需求更改的话也能更改好。

这里对header的作用描述不一定十分的准确，可以查看相关的文档了解详细详情：[HTTP 标头（header）](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers)，Ctrl+F直接一搜就到

#### 『主体』(body)

在前面的header定义完之后，需要各一行空行，才到http消息中的『主体』部分。

这个body部分就是这个请求真正想要传输的数据。

这部分可以是纯文本，也可以是二进制，都行，只要Content-Type说好了就行，没说的话，一般也行，但是得看后端处不处理了，一些后端框架不会帮你自动判断Body格式，或者不同的Content-Type处理的方式不同，如果请求的时候没有设置对，可能会有些麻烦事。

> 另外，上面的起始行/状态行和header行以及body和上面几行中间间隔的空行，和windows一样，使用的换行符都为CRLF，也就是`\r\n`，而不是linux/unix常用的`\n`。当然，这个知道就好了，库会帮咱们处理好的。

### HTTPS

HTTPS就是加密后的HTTP，'S'就是"Secure"。一般使用TLS来加密。原始的http消息经过全部加密后，再进行传输。只要中间偷听的人拿不到密钥，就不可能知道除了请求的ip以外任何的http信息。

关于现代TLS加密的机制，可以去看看[这篇文章](https://zhuanlan.zhihu.com/p/43789231)

一般约定http跑在80端口上，而https跑在443端口上，但是也可以用其他端口。

我的建议是，https能上就上。

## 主线任务：Servlet，Tomcat

### Servlet，介绍

在开始Tomcat的学习之前，咱们先来了解Servlet。

很多教程里都有讲过，就是运行在服务端的applet，也就是『Server Applet』。

说实话，当我听到“Applet”的时候...印象里貌似已经是上世纪早已失传的老东西了...但是其实现在咱们说的的“Server Applet”和那个客户端的“Applet”不是同一个东西，虽然都是当年就有的东西。

Servlet实际上就是一个接口。

咱们先来点开Servlet（清掉了注释）：

![Servlet.java](https://oss-img.ciduid.top/backend_tutorial/5.14/res/servlet.png "Servlet.java")

```java
package javax.servlet;

import java.io.IOException;

public interface Servlet {

    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig();

    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;

    public String getServletInfo();

    public void destroy();
}
```

是的，整个Servlet接口就这些代码。

那Servlet是干啥的？

大伙想想接口(interface)是用来干什么的？当然是一种规范约定。Servlet就是一种规范，所有实现Servlet的类，也就是处理网络请求的类要做什么。你要是不想按照Servlet来写，也是可以的，但是会少了很多库和框架的支持，很多东西就要你手动实现了。

初始化`init(ServletConfig config)`该做什么，接收到请求`service(ServletRequest req, ServletResponse res)`该做什么，服务器要退出了`destroy()`又该做什么，这些Servlet都不负责帮你实现，都需要你自己去实现。

这里的Servlet只是非常底层的Servlet定义，实际上，咱们学Servlet，其实就是学HttpServlet，学HttpServlet来实现一些非常简单的Web响应处理；咱们说的Servlet，其实就是实现了HttpServlet的自己写的类。

![HttpServlet.java](https://oss-img.ciduid.top/backend_tutorial/5.14/res/HttpServlet.png "HttpServlet.java")

HttpServlet根据Http对最原始的Servlet又封装了一层，也就是专门针对Http的Servlet。可以看到，相比于原来的几个方法，多了很多和http处理相关的方法。

HttpServlet最核心的方法就是`doGet(HttpServletRequest req, HttpServletResponse resp)`, `doPost(HttpServletRequest req, HttpServletResponse resp)`等等这种`doXxx`的方法，实际上就是对应请求中`GET`, `POST`等等请求的处理，当服务接受到这些请求的时候，就进入对应的方法处理，从req读请求，再向resp写响应。

恭喜你，当你接触到这里的时候，就已经到了开始入门写Web程序的阶段了，你现在在做的事就是十几年前的服务端程序员在干的事了，当年web服务还不复杂的时候，很多时候就是拿Servlet写页面渲染来实现web服务。

当然，只是实现了Servlet并不代表就是一个能跑的服务端程序了。是，我们是写了Servlet，但是Socket那些IO相关的东西咱还没有呢。

Servlet并不负责和客户端直接沟通，也就是说，那些Socket侦听8080啥的底层IO处理并不是在Servlet里面，实际的连接处理那是『Tomcat』那家伙来负责的。

### Tomcat，介绍

Tomcat是一个『Servlet容器』，也就是说，这玩意是个罐子，用来装一个个的Servlet程序。

![Tomcat](https://oss-img.ciduid.top/backend_tutorial/5.14/res/tomcat.png "Tomcat")

上文说到过，咱们虽然写了Servlet，但是没人负责和客户端建立连接沟通，所以，这个事就是Tomcat来做的了。

要知道，解析原始Http消息是一件非常麻烦的事情，光是Http标准那几百页有的是你看的了，你要干吗？反正我是不会自己干的，更何况还有各种IO相关的脏活累活要干。但是咱们的大好人——Tomcat，这些都让它来负责了。

Tomcat侦听着端口，客户端向我们这边发来了一个请求，Tomcat对客户端发来的Http请求进行解析，解析出请求路径，方法，header之类的信息，根据这些信息，把请求小心翼翼地包装好，封装成HttpServlettRequest，交给对应的Servlet去处理，然后Servlet处理完之后就返回HttpServletResponse给Tomcat，Tomcat就把这个响应又写成http消息，交给客户端。一个通过Servlet实现的服务端程序处理一个请求地过程就这么完成了。

### Servlet，开干！

说了这么多，估计大家手已经开始痒了。那就咱们现在就来开始写几个简单的HttpServlet玩玩！

#### 梦的开始，Hello World

计算机最经典的一句名言，莫过于那一声响彻云霄的 ***『Hello World!』***

现在，就来写个Servlet，当用浏览器访问的时候，能返回文本“Hello World!”

咱先来建一个简单的工程，什么框架都不用加，这样就好了嗼

![新建工程](https://oss-img.ciduid.top/backend_tutorial/5.14/res/new_project.png "新建工程")

很多JDK的发行版都没有带Servlet，因此如果写代码的时候没有Servlet和HttpServlet类，需要自己手动在Maven中引入依赖：

```xml
<!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api -->
<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>6.0.0</version>
    <scope>provided</scope>
</dependency>
```

注意，上面引用的是`jakarta.servlet`，因为19年oracle把javax送了出去，但是又不准继续使用javax这个名字，所以jdk11以后的版本用的都是jakarta作为包名，因此需要将名称改成jakarta。

也就是说，如果你还在用java 11以下的jdk，需要引入`javax.servlet`的servlet接口。

```xml
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
```

这些完事之后，就可以开撸了。

1. 继承HttpServlet类
    前文说过，要实现Servlet，就需要实现相应的方法。但是咱不可能又要从头写起，因此咱们继承的是HttpServlet类，而不是直接实现Servlet接口。

    ```java
    import jakarta.servlet.http.HttpServlet;

    public class HelloWorld extends HttpServlet {

    }
    ```

    这个时候，就可以去重写HttpServlet相应的`doXxx(HttpServletRequest req, HttpServletResponse resp)`方法来处理不同的Http请求了。

2. 重写`doXxx(HttpServletRequest req, HttpServletResponse resp)`

    > idea每日小技巧🥰

    简单，idea里边直接`Alt+Insert`，选择`Override Methods`

    ![Alt+Insert](https://oss-img.ciduid.top/backend_tutorial/5.14/res/insert.png "这只是一张图片")

    可以看到，有很多可以重写的方法：

    ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/override.png "可以重写的方法")

    咱们这里选择`doGet()`这项就好了。

    选好之后，嘣，idea就给我们生成好了重写方法了。

    ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/override_2.png "可以重写的方法")

    记得删除原来的`super.doGet(req, resp);`
    
    > 我就不删，咋的
    > 可能会导致服务端直接返回`400`或者`405`方法不支持错误给客户端，按住ctrl点开super的`doGet`就能看到了：
    > ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/super.png "super")
    
    往`doGet`方法添加（粘贴）这些内容：
    ```java
    PrintWriter writer = resp.getWriter();
    writer.print("Hello World!");
    writer.flush();
    ```

这样，一个直接向客户端（请求者）返回“Hello World!”字符串的Servlet就这样写完了，当下文Tomcat部署好之后，浏览器直接访问相应的页面，就能看到一个Hello World出现在你的浏览器屏幕。
    
就这？

是的。因为这确实就这么简单，但是当咱们业务复杂起来之后，那可就有得你写了。不过，那是后话了，咱们现在学的是原理，诚然框架非常好使，刷刷刷就写完了，但是总归要知道为什么这样，否则只是*只知其然，而不知其所以然*。

欸欸欸？不是说好要跑起来吗？

别急，咱还没搞好Tomcat呢

#### 梦的第二步，Tomcat

前面说到过，要让服务程序真正跑起来，还得Tomcat来干那些IO的脏活累活。

首先，得去[Tomcat的官网](https://tomcat.apache.org/)下载好Tomcat.

注意，Tomcat 10版本和9版本在Servlet的包路径上有很大的不同，原因同上，还是Oracle那家伙搞的麻烦...

如果你用的是`javax.servlet`请使用[Tomcat 9版本](https://tomcat.apache.org/download-90.cgi)；如果使用的是`jakarta.servlet`，请使用[Tomcat 10](https://tomcat.apache.org/download-10.cgi)。

下载完后，你能得到`apache-tomcat-10.1.8-windows-x64.zip`这样的压缩包，把它解压到你喜欢的地方。

1. 配置
   接下来，又是喜闻乐见的环境配置环节了🥰

   在系统变量中，添加`CATALINA_HOME`和`CATALINA_BASE`两个环境变量，指向你刚刚解压的Tomcat路径，如我这里解压到了`D:\apache-tomcat-10.1.8`，环境变量的值就填`D:\apache-tomcat-10.1.8`

   ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/location.png)

   配置好之后，直接运行bin里边的`startup.bat`在启动tomcat。

   如果控制台中文乱码了，可以修改conf下的`logging.properties`文件，将里边的`utf-8`替换为`936`，保存之后再启动，就好了

   如果一切成功，控制台的输出应该是像这样的：

   ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/tomcat_console.png)

   咱来conf文件夹瞧瞧。

   ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/conf.png)

   可以看到，有`server.xml`, `web.xml`和`content.xml`这三个配置文件。

   *麻了，怎么又是XML...*

   咱一个一个来看

   `server.xml`负责配置服务器相关的配置参数，如监听端口，最大连接数等等最底层的IO配置。
   ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/server.xml.png)

   `context.xml`配置的是servlet内容的路径等，tomcat服务器会定时去扫描这个文件。一旦发现文件被修改，就会自动重新加载这个文件，而不需要重启服务器。

   ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/content.xml.png)

   `web.xml`这家伙可就大了。不管虽然说是大，但其实不是很大，主要都是注解之类的。

   `web.xml`基本上可以不用动，只需要在咱们的Servlet类中用上`@WebServlet`注解，Tomcat就能好心帮我们挂载上去了。

   当然，如果你想自己手动写`web.xml`部署，也可以自己去了解了解，相信我，了解完之后你肯定会回来用注解的。

   打开你的浏览器，访问`http://127.0.0.1:8080`，如果你在`server.xml`里设置的端口不是`8080`，请以你的端口设置为准，如果一切正常，可以看到Tomcat默认的管理页面：
   ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/tomcat_index.png)

2. 做点小修改
   为了让Web部署更方便，咱们用注解来指定部署路径，用『Maven』来打包成war包进行部署。
   ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/web_servlet.png)

   图中加了个`@WebServlet("/yahaha")`，表明这个Servlet的路径是`/yahaha`，当访问到这个路径的时候，Tomcat就会去就调用我们写的这个Servlet。

   接着，为了让我们亲爱的的Maven把项目打包成war文件，向项目的`pom.xml`的添加以下内容:

   ```xml
   <packaging>war</packaging>
    <build>
        <finalName>learn</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
        </plugins>
    </build>
   ```
   ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/pom.png)

   好了之后，用maven打包，就得到了咱们想要的war文件了。

3. 放置War包
   把刚刚得到的war包拿出来（learn.war）。不难注意到，我们在`pom.xml`中，用`<finalName>learn</finalName>`指定了最后打包输出的文件名为`learn.war`，只要你喜欢，起什么名字都好。

   把它放到tomcat的`webapps`文件夹里。如果没有启动Tomcat，就把它启动起来。如果你已经把tomcat开起来了，放到`webapps`文件夹下面之后，就能看到这么一行日志：
   
   ```log
   14-May-2023 09:13:32.437 信息 [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployWAR 正在部署web应用程序存档文件[D:\apache-tomcat-10.1.8\webapps\learn.war]
   14-May-2023 09:13:32.465 信息 [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployWAR web应用程序存档文件[D:\apache-tomcat-10.1.8\webapps\learn.war]的部署已在[27]ms内完成
   ```

   同时也能看到`webapps`文件夹下面多出了个`learn`，Tomcat帮我们解压好了war包。

4. 让我看看！
   打开你的浏览器，访问`http://127.0.0.1:8080/learn/yahaha`，就能看到咱们写的Servlet输出了那令人激动的`Hello World!`了！
   ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/hello_world.png)

   注意，我们刚刚写的Servlet路径地址并不是`http://127.0.0.1:8080/yahaha`，而是`http://127.0.0.1:8080/应用名/servlet路径`，这里的应用名，就是咱们war包的名字。如果你想从根路径开始，可以把原来`webapps`里的`ROOT`文件夹删掉，也就是前面咱们看到的那个管理页面，然后将刚刚得到的`learn.war`该名成`ROOT.war`，然后像刚刚那样放到`webapps`文件夹下，咱们的这个war就会被部署挂载到根路径`/`下了，此时咱们写的Servlet路径就是`/yahaha`了。

   好了，这个就是你写的第一个Servlet了。能坚持走到这里，或许非常的不容易，看到这个`Hello World!`，或许可能会心中一股激动，这可是你写的第一个Web程序，这是从你的程序里，经过重重关卡发送到浏览器展示出来的文字，不亚于当年前辈们在地球上听到《东方红》时候的激动，指不定还会热泪盈眶，感慨万分；这么一行文字，可能倾注了你一天，或者好几天的时间和经历，但是这都没有关系，最终咱们还是做出来了，这是你自己第一次写出来的真正的Web程序，你这个时候，你已经正式踏上了后端开发的满满长路了。

   好了，先不发电了。

#### 意犹未尽，继续玩

就输出一个Hello World!怎么够？咱要玩大的！

![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/HttpServletRequest.png)

HttpServlet下有很多方法可以用，获取到的都是些Http参数，这些就是Tomcat帮我们解析好的，直接拿来就用。

例如，通过`getParameter(String name)`就能拿到请求参数，如`/yahaha?id=1234&type=none`中的`id`和`type`，可以通过`req.getParameter("id")`和`req.getParameter("type")`拿到相应的值，如果没有这个参数，获取到的就是`null`。这个看文档一看就知道。
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/doc.png)
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/doc_cn.png)

下面，咱们就来改写一下前面的Hello World，让他能够根据提供的参数，来输出不同的内容。
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/hellox.png)

像这样，通过`String id = req.getParameter("id");`拿到参数中的id，然后再在下面输出出来。

重新打包部署好，访问之前的地址：`http://127.0.0.1:8080/learn/yahaha`
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/hello_null.png)

啊，输出了`Hello null!`。这是因为咱们还没给参数呢，当然是null。

没给，那就给嘛。`http://127.0.0.1:8080/learn/yahaha?id=lensfrex`
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/hello_lensfrex.png)

这次咱给了id参数进去，可以看到，hello的内容已经不是null了，而是咱们提供的参数了。你喜欢的话，可以换成其他的参数，访问之后就能看到响应了不同的内容。

就这样，咱们的服务不是死的了，能根据请求的不同发生变化了。

到了这里，最基本的Servlet和Tomcat，大家应该是已经知道了。剩下的，就是去了解`HttpServletRequest`和`HttpServletResponse`的API用法了。这些文档就有很多的解释，也可以到处去看看教程，有很多实例可以学习学习。

### After Story

这里带大家了解了http, Servlet以及Tomcat，看起来好像很麻烦的样子。是的，就是很麻烦，但是上古程序员就是这么走来的。

现在咱们有了『SpringBoot』以及『Spring』一系列的框架，这玩意可以说是Java能如此流行的最重要的原因。当然，没有Spring，也会有Summer，也会有Winter。SpringBoot Web已经给我们内置了一个Tomcat，而且帮我们自动配置好了一大堆东西，这些在下一部分中陈姐会带你们领略领略。当你学完Spring的时候，就能知道这真的是个非常牛的玩意。

SpringBoot Web诚然用的非常爽，但是还是不要忘了本，不论上层怎么玩，还是要知道这玩意最基本的东西是怎么一回事的，Servlet还是要了解知道一些的，万变不离其宗，Spring再牛，最基础的还是这些。

当你玩转SpringBoot的web之后，再来回头看看的时候，哈，原来是这么一回事。

另外，Servlet容器不只是有Tomcat，他的同事还有`Jetty`, `GlassFish`, `Undertow`等等，只不过Tomcat因为SpringBoot Web默认内置，因此用的也是最多的，如果你喜欢，或者有需求，也可以换成其他的Servlet容器。

## DLC - 课外读物

[Servlet入门](https://www.liaoxuefeng.com/wiki/1252599548343744/1304265949708322)

[HTTP | MDN](https://developer.mozilla.org/zh-CN/docs/Web/HTTP)

[servlet的本质是什么，它是如何工作的？ - bravo1988的回答](https://www.zhihu.com/question/21416727/answer/690289895)

[servlet的本质是什么，它是如何工作的？ - 柳树的回答](https://www.zhihu.com/question/21416727/answer/339012081)

[Servlet到底是什么](http://c.biancheng.net/servlet2/what-is-servlet.html)

[一些Servlet应用的教程](https://www.runoob.com/servlet/servlet-form-data.html)