Servlet

[TOC]

第一章:初识Servlet

1.1-什么是Servlet及作用

Servlet(Server Applet)是Java Servlet的简称,称为小服务程序服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容

1.2-快速入门

步骤

  1. 创建JavaEE项目
  2. 定义一个类,实现Servlet接口中的方法
  3. 配置Servlet

演示

创建JavaEE项目

定义一个类ServletTest,实现Servlet接口方法

package cn.leilei.servletDemo;
import javax.servlet.*;
import java.io.IOException;
public class ServletTest implements Servlet {
  @Override
  public void init(ServletConfig servletConfig) throws ServletException {
    System.out.println("开启服务器并访问时,Servlet对象创建了");
  }

  @Override
  public ServletConfig getServletConfig() {
    return null;
  }

  @Override
  public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    System.out.println("执行了ServletTest程序");
  }

  @Override
  public String getServletInfo() {
    return null;
  }

  @Override
  public void destroy() {
    System.out.println("关闭服务器时,Servlet对象被销魂了");
  }
}

配置Servlet,进入web目录中找到web.xml文件,打开并进行配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <!--配置Servlet -->
    <servlet>
        <!--关联的类名-->
        <servlet-name>ServletTest</servlet-name>
        <servlet-class>cn.leilei.servletDemo.ServletTest</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>ServletTest</servlet-name>
        <!--虚拟路径-->
        <url-pattern>/test01</url-pattern>
    </servlet-mapping>
</web-app>

启动服务器,并访问http://localhost/tomcatDemo/test01 可以在控制开看到输出的内容,多次刷新会多次执行service方法中的程序。

1.3-Servlet执行原理

  1. 当服务器接收到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
  2. 查找web.xml文件,是否有对应的<url-pattern>标签体内容。
  3. 如果有,则在找到对应的<servlet-class>全类名
  4. tomcat会将字节码文件加载进内存,并且创建其对象
  5. 调用其方法

1.4-Servlet生命周期

  1. 被创建:执行init方法,只执行一次

默认情况下,第一次被访问时,Servlet被创建。

在在<servlet>标签下,可以配置执行Servlet的创建时机。

  • 第一次被访问时创建:值为负数<load-on-startup>-1</load-on-startup>
  • 在服务器启动时创建:值为0或正整数,<load-on-startup>0</load-on-startup>

Servlet的init方法,只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的

  • 多个用户同时访问时,可能存在线程安全问题。
  • 解决:尽量不要在Servlet中定义成员变量。即使定义了成员变量,也不要对修改值。
  1. 提供服务:执行service方法,执行多次

每次访问Servlet时,Service方法都会被调用一次。

  1. 被销毁:执行destroy方法,只执行一次

Servlet被销毁时执行。服务器关闭时,Servlet被销毁。

1.5-Servlet3.0的注解配置

之前版本存在的问题

在使用3.0之前的servlet版本实现动态生成web时,每提供一个访问服务类,都要在web.xml配置文件中进行相应的配置,若访问服务类较多时,web.xml会变得越来越臃肿且不易于维护。

新的方式:注解配置1

3.0及3.0之后,可以不需要web.xml配置文件,可以通过注解的方式实现配置。

在类上使用@WebServlet注解,进行配置,格式:@WebServlet("/资源路径")

代码如下:

package cn.leilei.servletDemo;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
// 注解
@WebServlet("/test02")
public class ServletTest02 implements Servlet {
  @Override
  public void init(ServletConfig servletConfig) throws ServletException {
    System.out.println("开启服务器并访问时,Servlet对象创建了");
  }

  @Override
  public ServletConfig getServletConfig() {
    return null;
  }

  @Override
  public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    System.out.println("执行了ServletTest程序");
  }

  @Override
  public String getServletInfo() {
    return null;
  }

  @Override
  public void destroy() {
    System.out.println("关闭服务器时,Servlet对象被销魂了");
  }
}

Servlet注解配置2

  • 一个Servlet可以定义多个访问路径 : 格式为 @WebServlet({"/路径名称1","/路径名称2","/路径名称3"})
  • 路径定义规则:
    1. /xxx
    2. /xxx/xxx
    3. *.do
      • *通配符,表示任意内容
package cn.leilei.ServletTest;

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

@WebServlet({"/test","/test/test03","*.do"})
public class Test03 extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println("post...");
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println("get...");
  }
}

1.6-IDEA与tomcat的相关配置

  1. IDEA会为每一个tomcat部署的项目单独建立一份配置文件
    • 服务器启动后查看控制台的log:Using CATALINA_BASE: C:\Users\Bruce\.IntelliJIdea2018.3\system\tomcat\Tomcat_8_5_31_tomcatDemo
  2. 工作空间项目 和 tomcat部署的web项目
    • tomcat真正访问的是“tomcat部署的web项目”,"tomcat部署的web项目"对应着"工作空间项目" 的web目录下的所有资源
    • WEB-INF目录下的资源不能被浏览器直接访问。
  3. 断点调试:使用"小虫子"启动 dubug 启动

第二章:Servlet体系结构

2.1-为什么要学习Servlet体系结构

在上一章节中,通过定义类实现Servlet接口动态处理Web时,我们可以发现实现类要实现所有Servlet方法,但是我们重点使用的是service方法,所以为了有选择的简化操作,我们需要学习Servlet相关的其他实现类及子类。

2.2-GenericServlet

GenericServlet是一个抽象类,通过自定义类继承GenericServlet类时,仅仅只需要实现service方法即可。因为GenericServlet类实现了Servlet接口中的其他方法,并以抽象方法的形式声明了service方法。

GenericServlet源码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package javax.servlet;

import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
  private static final long serialVersionUID = 1L;
  private transient ServletConfig config;

  public GenericServlet() {
  }

  public void destroy() {
  }

  public String getInitParameter(String name) {
    return this.getServletConfig().getInitParameter(name);
  }

  public Enumeration<String> getInitParameterNames() {
    return this.getServletConfig().getInitParameterNames();
  }

  public ServletConfig getServletConfig() {
    return this.config;
  }

  public ServletContext getServletContext() {
    return this.getServletConfig().getServletContext();
  }

  public String getServletInfo() {
    return "";
  }

  public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
  }

  public void init() throws ServletException {
  }

  public void log(String msg) {
    this.getServletContext().log(this.getServletName() + ": " + msg);
  }

  public void log(String message, Throwable t) {
    this.getServletContext().log(this.getServletName() + ": " + message, t);
  }

  public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

  public String getServletName() {
    return this.config.getServletName();
  }
}

自定义类实现GenericServlet

package cn.leilei.ServletTest;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
@WebServlet("/test01")
public class Test01 extends GenericServlet {
  // 重写sevice方法即可
  @Override
  public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    System.out.println("test01");
  }
}

2.3-HttpServlet

为什么要学习HttpServlet

在后端开发中,我们更多时候是在频繁地用技术处理客户端的请求,而客户端请求中,有两类请求是需要经常处理的,分别是getpost请求。

若我们通过继承Servlet体系结构中的GenericServlet类中的service方法处理客户端请求时,需要繁琐地解析客户端的请求种类(get或post),这样不便于提高程序的开发效率。

所以,Servlet体系结构中提供了HttpServlet抽象类,HttpServlet封装了对http协议的操作,简化代码,在自定义类继承HttpServlet时,需要通过重写doGet/doPost方法来分别处理客户端的get或post请求。

另外需要注意的是,HttpServlet继承了GenericServlet。

代码演示

客户端html页面代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="test02" method="post">
        <input type="text">
        <input type="submit" value="post请求">
    </form>
    <form action="test02" method="get">
        <input type="text">
        <input type="submit" value="get请求">
    </form>
</body>
</html>

服务端servlet代码:

package cn.leilei.ServletTest;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/test02")
public class Test02 extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println("get请求");
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println("post请求");
  }
}

第三章:Http

3.1-什么是Http及作用

Http(Hyper Text Transfer Protocol) 超文本传输协议,架构在TCP协议上 。

作用:规定WWW服务器与浏览器之间信息传递规范 。

3.2-Http特点

  • 基于TCP/IP的高级协议
  • 默认端口号:80
  • 基于请求/响应模型,一次请求对应一次响应
  • Http是无状态的,即服务器不保留与客户交易时的任何状态。这就大大减轻了服务器记忆负担,从而保持较快的响应速度 。

3.3-Http版本

  • 1.0版,每一次请求响应都会建立新的连接。
  • 1.1版,复用连接。

3.4-请求报文格式

格式

  1. 请求行
  2. 请求头
  3. 请求空行
  4. 请求体

请求行

请求方式 /请求url 请求协议/协议版本

如:GET /mil HTTP/1.1

请求方式,有七种请求方式,常用的有两种:

  • get
    1. 请求参数在请求行中,在url后。
    2. 请求的url长度有限制
    3. 不太安全
  • post
    1. 请求参数在请求体中
    2. 请求的url参数没有限制
    3. 相对安全

请求头

客户端浏览器会通过请求头告诉服务器一些信息。

以下便是请求百度http://news.baidu.com/mil?user=admin下的请求头信息

Host: news.baidu.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: BAIDUID=E2956C50745049A430004D077D15B934:FG=1; BIDUPSID=E2956C50745049A430004D077D15B934; PSTM=1576459419; cflag=13%3A3; delPer=0; H_PS_PSSID=1450_21089_30211_30284_26350; LOCALGX=%u77F3%u5BB6%u5E84%7C%32%35%31%7C%u77F3%u5BB6%u5E84%7C%32%35%31; Hm_lvt_e9e114d958ea263de46e080563e254c4=1577168870; Hm_lpvt_e9e114d958ea263de46e080563e254c4=1577168880
Referer: http://localhost:63342/ServletDemo/web/login.html?_ijt=icoj0khmoej93qjero91n76b3m

由上可以发现,请求头中的信息以key:value格式组织客户端信息。其中需要知道的是:

  • User-Agent:浏览器告诉服务器,使用的浏览器版本信息。

    • 通过此信息可以在服务端处理浏览器的兼容问题。
  • Referer:告诉服务器,我(当前请求)从哪里来。

    • 通过此信息可以实现防盗链技术

请求空行

空行,就是用于分割POST请求的请求头和请求体的。

请求体

post请求的参数在请求体中。

3.5-响应报文格式

格式

  1. 响应行
  2. 响应头
  3. 响应空行
  4. 响应体

响应行

组成:协议/版本 响应状态码 状态码描述

响应状态码:服务器告诉客户端浏览器本次请求和响应的一个状态。状态码是3位数字。分类如下:

  1. 1xx,服务器接收客户端消息,但没有接收完成,等待一段时间后,发送1xx多状态码。
  2. 2xx,成功。代表:200
  3. 3xx,重定向。代表:302(重定向),304(访问缓存)
  4. 4xx,客户端错误。
    • 404(请求路径没有对应的资源)
    • 405:请求方式没有对应的doXxx方法
  5. 5xx,服务器端错误。代表:500(服务器内部出现异常)

响应头

组织消息格式:key:value

常见的响应同消息:

  1. Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式。
  2. Content-disposition:服务器告诉客户端以什么格式打开响应体数据。
    • in-line:默认值,在当前页面内打开
    • attachment;filename=xxx:以附件形式打开响应体。文件下载

响应体

服务端向客户端传输的数据

第四章:Request对象

4.1-概述

若我们在服务端实现处理客户端请求和处理响应时,需要学习两个对象,分别是:

  1. Request
  2. Response

request和response对象是由服务器创建的。

request对象是来获取请求消息,response对象是来设置响应消息。

本章先学习Request对象。

4.2-Request对象体系结构

4.3-获取请求消息数据

方法如下:

  • 获取请求方式:String getMethod()
  • 获取虚拟目录:String getContextPath()
  • 获取Servlet路径:String getServletPath()
  • 获取get方式请求参数:String getQueryString()
  • 获取请求的URI:String getRequestURI()
  • 获取请求的URL:StringBuffer getRequestURL()
  • 获取协议版本:String getProtocol()
  • 获取客户机ip地址:String getRemoteAddr()

代码演示

请求路径:http://localhost/requestdemo/test01?user=admin&pwd=abc

package it.leilei.RequtestTest;

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

@WebServlet("/test01")
public class Test01 extends HttpServlet {
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // - 获取请求方式:`String getMethod()`
    System.out.println(req.getMethod());      // 结果:GET
    // - 获取虚拟目录:**`String getContextPath()`**
    System.out.println(req.getContextPath()); // 结果:/requestdemo
    // - 获取Servlet路径:`String getServletPath()`
    System.out.println(req.getServletPath()); // 结果:/test01
    // - 获取get方式请求参数:`String getQueryString()`
    System.out.println(req.getQueryString()); // 结果:user=admin&pwd=abc
    // - 获取请求的URI:`String getRequestURI()`
    // 统一资源标识符
    System.out.println(req.getRequestURI()); // 结果:/requestdemo/test01
    // - 获取请求的URL:`StringBuffer getRequestURL()`
    // 统一资源定位符
    System.out.println(req.getRequestURL()); // 结果:http://localhost/requestdemo/test01
    // -获取协议及版本:String getProtocol()
    System.out.println(req.getProtocol());   // 结果:HTTP/1.1
    // -获取客户机ip地址
    System.out.println(req.getRemoteAddr()); // 结果:0:0:0:0:0:0:0:1
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    this.doPost(req,resp);
  }
}

4.4-获取请求头数据

方法

  • 根据请求头名称,获取对应的值:String getHeader(String name)
  • 获取所有的请求头名称:Enumeration<String> getHeaderNames()

代码

package it.leilei.RequtestTest;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

@WebServlet("/test02")
public class Test02 extends HttpServlet {
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    Enumeration<String> headerNames = req.getHeaderNames();
    // 循环遍历,并检测headerNames是否有更多的元素
    while (headerNames.hasMoreElements()){
      // 获取一个元素
      String name = headerNames.nextElement();
      // 根据请求头名称获取对应的值
      String  v = req.getHeader(name);
      System.out.println(name + ":" + v);
      System.out.println("--------------");
    }

  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    this.doPost(req,resp);
  }
}

4.5-获取请求体数据

方法

请求体:只有POST请求方式,才有请求体,在请求体中封装了POST请求的请求参数。

  • BufferedReader getReader():获取字符输入流,只能操作字符数据
  • ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据

代码

html代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="test03" method="post">
        <input type="text" name="username">
        <input type="text" name="pwd">
        <input type="submit" value="提交">
    </form>
</body>
</html>

servlet代码

package it.leilei.RequtestTest;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Enumeration;
@WebServlet( "/test03")
public class Test03 extends HttpServlet {
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    BufferedReader reader = req.getReader();
    String line = null;
    while ((line=reader.readLine())!=null){
      System.out.println(line);
    }
    // 结果:username=zhagnsan&pwd=aaa
  }
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    this.doPost(req,resp);
  }
}

4.6-获取请求参数通用方式

不论get还是post请求方式都可以使用下列方法来获取请求参数

方法

  • String getParameter(String name):根据参数名称获取参数值
  • String[] getParameterValues(String name):根据参数名称获取参数值的数组
  • Enumeration<String> getParameterNames():获取所有请求的参数名称
  • Map<String,String[]> getParameterMap():获取所有参数的map集合

关于获取参数值的乱码问题

  • get方式:tomcat 8 已经将get方式乱码问题解决了
  • post方式:会乱码
    • 解决:在获取参数前,设置request的编码request.setCharacterEncoding("utf-8");

代码

html代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="test04" method="get">
    <input type="text" name="user" placeholder="用户名"><br>
    <input type="text" name="pwd" placeholder="密码"><br>
    <input type="checkbox" name="hobby" value="足球">足球
    <input type="checkbox" name="hobby" value="篮球">篮球
    <input type="checkbox" name="hobby" value="拍球">拍球
    <input type="checkbox" name="hobby" value="乒乓球">乒乓球
    <br>
    <input type="submit" value="提交">
</form>
</body>
</html>

servlet代码

package it.leilei.RequtestTest;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;

@WebServlet("/test04")
public class Test04 extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //- `String getParameter(String name)`:根据参数名称获取参数值
    System.out.println(request.getParameter("user"));
    System.out.println("----------------------------");
    //- `String[] getParameterValues(String name)`:根据参数名称获取参数值的数组
    String[]values = request.getParameterValues("hobby");
    for (String v : values) {
      System.out.println(v);
    }
    System.out.println("----------------------------");
    //- `Enumeration<String> getParameterNames()`:获取所有请求的参数名称
    Enumeration<String> parameterNames = request.getParameterNames();
    while (parameterNames.hasMoreElements()){
      String name = parameterNames.nextElement();
      System.out.println(request.getParameter(name));
    }
    System.out.println("----------------------------");
    //- `Map<String,String[]> getParameterMap()`:获取所有参数的map集合
    Map<String, String[]> parameterMap = request.getParameterMap();
    Set<String> keys = parameterMap.keySet();
    for (String key : keys) {
      System.out.println(key + ":" + Arrays.toString(parameterMap.get(key)));
    }

  }
}

4.7-请求转发

什么是请求转发

一种在服务器内部的资源跳转方式。

步骤

  1. 通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)
  2. 使用RequestDispatcher对象来进行转发:forward(ServletRequest request, ServletResponse response)

特点

  1. 浏览器地址栏路径不发生变化
  2. 只能转发到当前服务器内部资源中。
  3. 转发是一次请求

代码演示

Servlet代码-test05,访问test05并会跳入test06中

package it.leilei.RequtestTest;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/test05")
public class Test05 extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println("欢迎进入test05");
    // 获取进入test06的转发器并跳转到test06
    request.getRequestDispatcher("/test06").forward(request,response);

  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request,response);
  }
}

Servlet代码-test06

package it.leilei.RequtestTest;

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

@WebServlet("/test06")
public class Test06 extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println("欢迎进入test06");
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request,response);
  }
}

4.8-共享数据

域对象

域对象:一个有作用范围的对象,可以在范围内共享数据

request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据

方法

  • void setAttribute(String name,Object obj):存储数据
  • Object getAttitude(String name):通过键获取值
  • void removeAttribute(String name):通过键移除键值对

代码

请求路径:http://localhost/requestdemo/servletA?userName=zhangsan

servletA代码:

package it.leilei.RequtestTest;

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

@WebServlet("/servletA")
public class ServletA extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.getRequestDispatcher("/servletB").forward(request,response);
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request,response);
  }
}

ServletB代码:

package it.leilei.RequtestTest;

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

@WebServlet("/servletB")
public class ServletB extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("utf-8");
    System.out.println("欢迎进入ServletB");
    System.out.println("您的名字是:" + request.getParameter("userName"));
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request,response);
  }
}

第五章:登录案例

5.1-需求

5.2-步骤分析

5.3-开发步骤

01-创建项目,导入html页面、配置文件、jar包

02-数据准备

CREATE DATABASE db5;
CREATE TABLE USER(
    id INT PRIMARY KEY AUTO_INCREMENT,
    userName VARCHAR(32) NOT NULL,
    pwd VARCHAR(32) NOT NULL
);
INSERT INTO USER VALUES(NULL,zhagnsan,123456),(NULL,lisi,abcdef);

03-创建cn.leilei.domain包中实体类User

package cn.leilei.domain;
/*用户实体类*/
public class User {
  private int id;
  private String userName;
  private String pwd;

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getUserName() {
    return userName;
  }

  public void setUserName(String userName) {
    this.userName = userName;
  }

  public String getPwd() {
    return pwd;
  }

  public void setPwd(String pwd) {
    this.pwd = pwd;
  }

}

04-创建cn.leilei.util包下的DruidUtils工具

package cn.leilei.util;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/*
* 数据库连接池工具类
* */
public class DruidUtils {
  private static DataSource ds;
  private static InputStream is;
  static {
    Properties pro = new Properties();
    try {
      // 读取配置文件
      is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
      pro.load(is);
      // 创建数据库连接池对象
      ds = DruidDataSourceFactory.createDataSource(pro);
    } catch (IOException e) {
      e.printStackTrace();
    } catch (Exception e) {
      e.printStackTrace();
    }finally {
      if(is!=null){
        try {
          is.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

  }
  // 获取Connection对象
  public static Connection getConnection() throws SQLException {
    return ds.getConnection();
  }
  // 释放资源
  public static void close(Statement sta, Connection con, ResultSet rs) {
    if(sta!=null){
      try {
        sta.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }

    }
    if(con!=null){
      try {
        con.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
    if(rs!=null){
      try {
        rs.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
  }
  public static void close(Statement sta, Connection con){
    close(sta,con,null);
  }
  // 获取连接池对象
  public static DataSource getDataSource(){
    return ds;
  }
}

05-创建cn.leilei.dao包下的UserDao类,提供login方法

package cn.leilei.dao;

import cn.leilei.domain.User;
import cn.leilei.util.DruidUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
/*操作数据库user表的类*/
public class UserDao {
  // 创建JdbcTemplate对象
  JdbcTemplate jdbcTemplate = new JdbcTemplate(DruidUtils.getDataSource());
  // 查询数据库中是否存在指定的用户
  public User login(User loginUser){
    try{
      // 定义sql
      String sql = "select * from user where userName=? and pwd=?";
      // 执行
      User user = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<User>(User.class),loginUser.getUserName(),loginUser.getPwd());
      return user;
    }catch (Exception e){
      e.printStackTrace();
      return null;
    }
  }
}

06-编写cn.leilei.web.servlet包中的LoginServlet类,处理登录请求

package cn.leilei.web.servlet;

import cn.leilei.dao.UserDao;
import cn.leilei.domain.User;
import org.apache.commons.beanutils.BeanUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    User loginUser = new User();
    Map<String, String[]> parameterMap = request.getParameterMap();
    try {
      BeanUtils.populate(loginUser,parameterMap);
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      e.printStackTrace();
    }
    UserDao userdao = new UserDao();
    User u = userdao.login(loginUser);
    if(u==null){
      request.getRequestDispatcher("/failed").forward(request,response);
    }else {
      request.setAttribute("user",u);
      request.getRequestDispatcher("/success").forward(request,response);
    }

  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
   this.doPost(request,response);
  }
}

07-编写cn.leilei.web.servlet包下的FiledServlet类处理登录失败时情况

package cn.leilei.web.servlet;

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

@WebServlet("/failed")
public class FailedServlet extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("utf-8");
    response.setContentType("text/html;charset=utf-8");
    response.getWriter().write("<h1>登录失败!</h1>");
    response.getWriter().write("<h1>用户名或密码错误!</h1>");
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request,response);
  }
}

08-编写cn.leilei.web.servlet包下的SuccessServlet类处理登录成功时情况

package cn.leilei.web.servlet;

import cn.leilei.domain.User;

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

@WebServlet("/success")
public class SuccessServlet extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("utf-8");
    User user = (User)request.getAttribute("user");
    response.setContentType("text/html;charset=utf-8");
    response.getWriter().write("<h1>登录成功!</h1>");
    response.getWriter().write("<h1>欢迎【"+user.getUserName()+"】!</h1>");
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
  }
}

5.4-BeanUtils工具类

可以简化数据封装

org.apache.commons.beanutils.BeanUtils;

jar包下载

链接:https://pan.baidu.com/s/1skOTTH8xOBIXih7GZ_CHOw
提取码:fo7q

常用方法

  1. setProperty(Object bean,String name,Object value)
  2. getProperty(Object bean, String name)
  3. populate(Object bean , Map map):将map集合的键值对信息,封装到对应的JavaBean对象中

第六章:Response对象

6.1-设置响应消息

设置状态码

  • 方法:setStatus(int sc)

设置响应头

  • 方法:setHeader(String name, String value)

设置响应体步骤

01-获取输出流

  • 字符输出流:PrintWriter getWriter()
  • 字节输出流:ServletOutputStream getOutputStream()

02-使用输出流

代码演示

package cn.leilei.ServletTest;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/test07")
public class Test07 extends HttpServlet {
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 设置状态码
    resp.setStatus(200);
    // 设置响应头,要在流之前设置
    resp.setHeader("Content-type","text/html;charset=utf-8");
    // 设置Content-type简化方式
    // res.setContentType("text/html;charset=utf-8");
    // 字符输出流
    //PrintWriter writer = resp.getWriter();
    //writer.write("<h1>Hello</h1>");
    // 字节输出流;获取的流的默认编码是ISO-8859-1
    ServletOutputStream outputStream = resp.getOutputStream(); 
    outputStream.write("<h2>Servlet</h2>".getBytes());
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    this.doPost(req,resp);
  }
}

6.2-重定向

重定向的方式

方式1:response.setHeader("location", "跳转的资源路径");

方式2:response.sendRedirect("跳转的资源路径");

图解重定向

重定向和转发的特点

重定向(redirect)的特点:

  1. 客户端地址栏发生变化
  2. 重定向可以访问其他站点(服务器)的资源。
  3. 重定向是两次请求,不能使用request共享数据

转发(forward)的特点:

  1. 客户端地址了路径不发生变化
  2. 转发只能访问当前服务器下的资源
  3. 转发是一次请求,可以使用request共享数据

代码演示

Test04

package cn.leilei.ServletTest;

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

@WebServlet("/test04")
public class Test04 extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //【重定向方式1】
    // 1. 设置状态码
    response.setStatus(302);
    // 2. 设置路径
    // response.setHeader("location", "/servletDemo/test05");
    // 【重定向方式2】
    response.sendRedirect("/servletDemo/test05");
    System.out.println("test04...");

  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request, response);
  }
}

Test05

package cn.leilei.ServletTest;

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

@WebServlet("/test05")
public class Test05 extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println("test05...");
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request,response);
  }
}

6.3-路径写法

相对路径

格式:相对路径无法确定唯一资源。

  • 不是以/开头,而是以./开头,如./index.html

规则:找到当前资源和目标资源之间的相对位置关系

  • ./当前目录
  • ../上一层目录

绝对路径

格式:通过绝对路径可以确定唯一资源。

  • /开头的路径,如http://localhost/servletDemo/register.html/servletDemo/register.html

规则:判断定义的路径是给谁用的?

  • 给客户端使用,需要加虚拟目录(项目的访问路径)
    • 建议虚拟目录动态获取:request.getContextPath()
  • 给服务器使用,不需要加虚拟目录(转发路径)

6.4-验证码案例

验证码的目的主要是为了防止表单的恶意操作(如注册)

需求

点击图片或超链接时切换验证码图片。

验证码图片上的内容和线条是随机的,由服务端提供。

实现步骤

  1. 构建html页面
  2. 加载页面时或点击图片或超链接时,图片资源需要向服务端请求
  3. 服务服务端绘制图片,然后以流的方式响应客户端。

实现代码

表单页面代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <p>
      <span>验证码:</span>
      <!--绝对路径-->
      <img src="/servletDemo/checkCode" alt="">
      <a href="#">看不清?换一张</a>
      <script>
          var imgNode = document.querySelector("img");
          var aNode = document.querySelector("a");
          aNode.onclick = imgNode.onclick = function () {
              // 追加变化的参数,解决因图片缓存问题而不变化
              imgNode.src = "/servletDemo/checkCode?" + new Date().getTime();
          };

      </script>
  </p>
</body>
</html>

sevlet代码

package cn.leilei.checkCodeDemo;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

@WebServlet("/checkCode")
public class CheckCodeServlet extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    int width=100;
    int height = 50;
    // 1.创建一个图片对象
    BufferedImage bImg = new BufferedImage(width,height,BufferedImage.TYPE_INT_BGR);

    // 2.美化图片
    // 2.1 填充颜色
    // 获取画笔
    Graphics graphics = bImg.getGraphics();
    // 设置颜色
    graphics.setColor(Color.darkGray);
    // 填充颜色
    graphics.fillRect(0,0,width,height);
    // 2.2 绘制边框
    graphics.setColor(Color.pink);
    graphics.drawRect(0,0,width-1,height-1);
    // 2.3 绘制文字
    Random random = new Random();
    String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
    for (int i = 1; i <= 4; i++) {
      char charStr = str.charAt(random.nextInt(str.length()));
      graphics.drawString(charStr + "",width/5*i,height/2+6);
    }
    // 2.4 绘制干扰线
    for (int i = 0; i < 10; i++) {
      float[]fs={};
      graphics.setColor(Color.cyan);
      int x1 = random.nextInt(width);
      int x2 = random.nextInt(width);
      int y1 = random.nextInt(height);
      int y2 = random.nextInt(height);
      graphics.drawLine(x1,y1,x2,y2);
    }
    // 3.将图片输出到页面
    ImageIO.write(bImg,"jpg",response.getOutputStream());
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request,response);
  }
}

第七章:ServletContext对象

一个ServletContext对象代表整个web应用,可以和程序的容器(服务器)通信。

7.1-获取ServletContext对象

  1. 通过request对象获取
    • request.getServletContext()
  2. 通过HttpServlet对象获取
    • this.getServletContext()

7.2-获取MIME类型

MIME介绍

多用途互联网邮件扩展,它是一个互联网标准,在1992年最早应用于电子邮件系统,但后来也应用到浏览器服务器会将它们发送的多媒体数据的类型告诉浏览器,而通知手段就是说明该多媒体数据的MIME类型,从而让浏览器知道接收到的信息哪些是MP3文件,哪些是Shockwave文件等等。服务器将MIME标志符放入传送的数据中来告诉浏览器使用哪种插件读取相关文件

格式是:大类型/小类型;如:text/html、 image/jpeg

方法

String getMimeType(String file)

该方法会根据文件名,自动的去web.xml配置文件中获取对应的MIME标志符。

7.3-域对象

ServletContext对象范围:所有用户所有请求的数据。

方法:

  1. setAttribute(String name,Object value)
  2. getAttribute(String name)
  3. removeAttribute(String name)

7.4-获取文件在服务器上的真实路径

方法

String getRealPath(String var1);

代码

package cn.leilei.ServletContextDemo;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/serverContext02")
public class Test02Servlet extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    ServletContext servletContext = this.getServletContext();
    // 获取web根目录下的资源真实路径
    String str1 = servletContext.getRealPath("./a.txt");
    //C:\Users\Bruce\Desktop\Gitee\JavaWeb\ServletDemo\out\artifacts\ServletDemo_war_exploded\a.txt
    System.out.println(str1);

    // 获取src目录下的资源路径
    String str2 = servletContext.getRealPath("./WEB-INF/classes/a.txt");
    System.out.println(str2);
    // C:\Users\Bruce\Desktop\Gitee\JavaWeb\ServletDemo\out\artifacts\ServletDemo_war_exploded\WEB-INF\classes\a.txt

    // 获取WEB-INFO目录下的资源路径
    String str3 = servletContext.getRealPath("./WEB-INF/a.txt");
    System.out.println(str3);
    // C:\Users\Bruce\Desktop\Gitee\JavaWeb\ServletDemo\out\artifacts\ServletDemo_war_exploded\WEB-INF\a.txt
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request,response);
  }
}

7.5-文件下载案例

需求

点击超链接下载任意类型的文件

步骤分析

  1. 定义html页面
  2. 定义处理文件名乱码工具类
  3. 定义Servlet处理请求程序
    1. 获取文件名参数值fileName
    2. 创建servletContent对象
    3. 获取文件的真实路径
    4. 创建字节输入流对象,根据文件真实路径获取服务器上的资源
    5. 根据文件名获取MIME类型的值
    6. 设置响应头,Content-type的MIME类型
    7. 通过文件名乱码处理类,处理文件名乱码问题
    8. 设置响应头,Content-disposition的值为attachment;fileName=文件名
    9. 循环读取本地文件数据,边读取边通过响应输出流输出

html文件代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
        </head>
<body>
<a href="/photoLoad/loadP?fileName=itxia.png">下载图片-itxia</a>
<a href="/photoLoad/loadP?fileName=原力比特.png">下载图片-原力比特</a>
</body>
</html></title>
</head>
<body>

</body>
</html>

文件名乱码处理类

package cn.it.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;


public class DownLoadUtils {
  public static String getFileName(String agent, String filename) throws UnsupportedEncodingException {
    if (agent.contains("MSIE")) {
      // IE浏览器
      filename = URLEncoder.encode(filename, "utf-8");
      filename = filename.replace("+", " ");
    } else if (agent.contains("Firefox")) {
      // 火狐浏览器 JDK8支持BASE64Encoder
      BASE64Encoder base64Encoder = new BASE64Encoder();
      filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
    } else {
      // 其它浏览器
      filename = URLEncoder.encode(filename, "utf-8");
    }
    return filename;
  }
}

servlet类

import cn.it.util.DownLoadUtils;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;

@WebServlet("/loadP")
public class loadP extends HttpServlet {
  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 获取文件名
    String fileName = request.getParameter("fileName");
    // 获取servletContext对象
    ServletContext servletContext = this.getServletContext();

    // 获取在服务器上的真实路径
    String path = servletContext.getRealPath("./img/" + fileName);
    // 获取响应输出流
    ServletOutputStream os = response.getOutputStream();
    // 获取对应的MIME类型
    String mime = servletContext.getMimeType(fileName);
    // 设置对应的MIME类型
    response.setHeader("Content-type", mime);
    // 解决中文名文件乱码问题
    String agent = request.getHeader("user-agent");
    fileName = DownLoadUtils.getFileName(agent, fileName);
    // 设置响应头
    response.setHeader("Content-disposition", "attachment;filename=" + fileName);
    // 根据路径读取本地文件-Input流
    FileInputStream fis = new FileInputStream(path);
    byte[] bts = new byte[1024 * 8];
    int len = 0;
    while ((len = fis.read(bts)) != -1) {
      os.write(bts, 0, len);
    }
    fis.close();

  }

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request, response);
  }
}


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容