第三章 请求与响应

  • 3.1 从容器到 HttpServlet

Web容器做了什么 

当浏览器请求HTTP服务器时,是使用HTTP来传送请求与相关信息(标头、请求、参数、Cookie等)。HTTP是基于TCP/IP之上的协议,信息基本上都是通过文字信息来传送的,然而Servlet本质上市个Java对象,运行于Web容器中。

当请求来到HTTP服务器,而HTTP服务器转交给容器时,容器会创建一个代表当次请求的HttpServletRequest对象,并将请求相关信息设置给该对象。同时容器会创建一个HttpServletResponse对象,作为稍后要对客户端进行响应的Java对象。

接着,容器会根据读取的@WebServlet标注或web.xml的设置,找出处理该请求的Servlet,调用它的service()方法,将创建的HttpServletRequest对象、HttpServletResponse对象传入作为参数,service方法中会根据HTTP请求的方式,调用对应的doXXX()方法。例如,若为GTE则调用doGet()。

接着在doGet()中,可以使用HttpServletRequest对象、HttpServletResponse对象。例如使用getParameter()取得请求参数,使用getWriter()取得输出用的PrintWriter对象,并进行各项响应处理。对PrintWriter做的输出操作,最后由容器转换为HTTP响应,再由HTTP服务器对浏览器进行响应。之后容器将HttpServletRequest对象、HttpServletResponse对象销毁回收,该次请求响应结束。

像这类请求/响应对象的创建与销毁,也就是有关请求/响应对象的生命周期管理,也是Web容器提供的功能。

doXXX()方法

Servlet接口的service()方法签名其实接受的是ServletRequest、ServletResponse:

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException;

请求/响应对象的基本行为是规范在ServletRequest、ServletResponse(包是javax.servlet),而与HTTP相关行为则分别由两者的子接口HttpServletRequest、HttpServletResponse(包是javax.servlet.http)定义。

Web容器创建的确实是HttpServletRequest、HttpServletResponse的实现对象,而后调用Servlet接口的service()方法。在HttpServlet中实现service()如下:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    HttpServletRequest request;
    HttpServletResponse response;
    try {
        request = (HttpServletRequest)req;
        response = (HttpServletResponse)res;
    } catch (ClassCastException var6) {
        throw new ServletException("non-HTTP request or response");
    }
    this.service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    long errMsg;
    if(method.equals("GET")) {
        errMsg = this.getLastModified(req);
        if(errMsg == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader("If-Modified-Since");
            if(ifModifiedSince < errMsg / 1000L * 1000L) {
                this.maybeSetLastModified(resp, errMsg);
                this.doGet(req, resp);//处理HTTP GET请求
            } else {
                resp.setStatus(304);
            }
        }
    } else if(method.equals("HEAD")) {
        errMsg = this.getLastModified(req);
        this.maybeSetLastModified(resp, errMsg);
        this.doHead(req, resp);//处理HTTP HEAD请求
    } else if(method.equals("POST")) {
        this.doPost(req, resp);//处理HTTP POST请求
    } else if(method.equals("PUT")) {
        this.doPut(req, resp);//处理HTTP PUT请求
    } else if(method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if(method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if(method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg1 = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg1 = MessageFormat.format(errMsg1, errArgs);
        resp.sendError(501, errMsg1);
    }
}

 如果客户端发出了没有实现的请求:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String protocol = req.getProtocol();
    String msg = lStrings.getString("http.method_get_not_supported");
    if(protocol.endsWith("1.1")) {
        resp.sendError(405, msg);
    } else {
        resp.sendError(400, msg);
    }
}

 如果在继承HttpServlet之后,没有重新定义doGet()方法,而客户端对该servlet发出了GET请求,则会收到错误信息。

在GET与POST都要相同处理的情境下,通常可以继承HttpServlet之后,在doGet()、doPost()中都调用一个自定义的processRequest()。如:

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    processRequest(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    processRequest(req, resp);
}
protected void processRequest(HttpServletRequest req, HttpServletResponse resp) throws  ServletException,IOException {
    //处理请求
}
  • 3.2 关于HttpServletRequest

处理请求参数与标头

getParameter():指定请求参数名称来取得对应的值。

String username = request.getParameter("name");

getParameter()返回的是String对象,若传来的是像“123”这样的字符串值,而需要的是基本数据类型,则必须使用Integer.parseInt()这类的方法将之剖析为基本类型。若请求中没有所指定的请求参数名称,则返回null。

getParameterValue():如果窗体上有可复选的元件,如复选框、列表等,则同一个请求参数名称会有多个值(param=10&param=20&param=30),此时可以用可以用getParameterValues()取得一个String数组,数组元素代表所有被选取选项的值。

String[] values = request.getParameterValues("param");

getParameterNames():请求中有多少个请求参数,返回一个Enumeration对象,其中包括所有的请求参数名称。

Enumeration<String> e = req.getParameterNames();
while(e.hasMoreElements()) {
  String param = e.nextElement();
      ...
}

 getParameterMap():将请求参数以Map对象返回,Map中的键(Key)是请求参数名称,值(Value)的部分是请求参数值,以字符串数组类型String[]返回。

对于HTTP的标头(Header)信息

getHeaders():使用方式与getParameterValues()类似,指定标头名称后可返回Enumeration,元素为字符串

getHeaderNames():使用方式与getParameterValues()类似,取得所有标头名称,以Enumeration返回,内含所有标头字符串名称。

package cc.openhome;

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

@WebServlet("/header.view")
public class HeaderServlet extends HttpServlet{
/* (non-Javadoc)
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
PrintWriter out = resp.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>HeaderServlet</title>");
out.println("</head>");
out.println("<body>");
//取得应用程序环境路径
out.println("<h1>HeaderServlet at " + req.getContextPath() + "</h1>");
//取得所有标头名称
Enumeration<String> names = req.getHeaderNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
//取得标头值
out.println(name + ":" + req.getHeader(name) + "<br>");
}
out.println("</body>");
out.println("</html>");
out.close();
}
}

请求参数编码处理

1、POST请求参数编码处理

如果客户端没有在Content-Type标头中设置字符编码信息,此时使用HttpServletRequest的getCharacterEncoding()返回值回事null。这个情况下,容器若使用的是默认编码处理是ISO-8859-1,而客户端使用UTF-8发送非ASCII字符的请求参数,Servlet直接使用getParameter()等方法取得该请求参数值,就会乱码。可以使用HttpServletRequest的setCharacterEncoding()方法指定取得POST请求参数时使用的编码(req.setCharacterEncoding("UTF-8")相当于String text = java.net.URLDecoder.decode("%E6%9E%97", "UTF-8"))

2、GET请求参数编码处理

在HttpServletRequest的API文件中,对setCharacterEncoding()的说明清楚提到:

Overrides the name of the character encoding used in the body of this request.

这个方法对于请求Body中的字符编码才有作用,也就是基本上这个方法只对POST产生作用,当请求时用GET发送时,则没有定义这个方法是否会影响Web容器处理编码的方式。(主要是因为处理URL的是HTTP服务器,而非Web容器)。若使用Tomcat并采用GET,或并没有设置setCharacterEncoding(),且已取得一个请求参数字符串,另外一个处理编码的方式,则是通过String的getBytes()指定编码来取得该字符串的字节数组,然后再重新构造为正确编码的字符串。

例如,若浏览器使用UTF-8处理字符,Web容器默认使用ISo-8859-1编码,则正确处理编码的方式为:

String name = req.getParameter("name");
String name = new String(name.getBytes("ISO-8859-1"), "UTF-8");

相当于

String text = java.net.URLDecoder.encode("林", "UTF-8");
package cc.openhome;

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

@WebServlet("/encoding")
public class EncodingServlet extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //GET的编码处理
        String name = req.getParameter("nameGet");
        name = new String(name.getBytes("ISO-8859-1"), "UTF-8");
        System.out.println("GET: " + name);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //POST的编码处理
        resp.setCharacterEncoding("UTF-8");
        String name = req.getParameter("namePost");
        System.out.println("POST:" + name);
    }
}

getReader()、getInputStream()读取Body内容

package cc.openhoem;

import javax.servlet.ServletException;
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.io.PrintWriter;

/**
 * Created by Administrator on 2016/4/14.
 * 上传文件
 */
public class BodyServlet extends HttpServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String body = readBody(req);
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Servlet BodyView</title>");
        out.println("<head>");
        out.println("<body>");
        out.println(body);
        out.println("</body>");
        out.println("/html");
    }

    private String readBody(HttpServletRequest req) throws IOException {
        BufferedReader reader = req.getReader();
        String input = null;
        String requestBody = "";
        while(null != (input = reader.readLine())) {
            requestBody = requestBody + input + "<br>";
        }
        return requestBody;
    }
}

对应的窗体

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title></title>
  </head>
  <body>
    Hello
    <form action="BodyServlet" method="post" enctype="multipart/form-data">
      选择文件:<input type="file" name="filename" value="" /><br>
      <input type="submit" value="Upload" name="upload">
    </form>
  </body>
</html>

getPart()、getParts()取得上传文件

<html>
  <head>
    <title></title>
  </head>
  <body>
    Hello
    <form action="UploadServlet" method="post" enctype="multipart/form-data">
      上传照片:<input type="file" name="photo" /><br><br>
      <input type="submit" value="上传" name="upload">
    </form>
  </body>
</html>
/**
 * @Title: UploadServlet.java
 * @Package cc.openhome
 * @Description: TODO(文件上传)
 * @author rocky
 * @date 2016年4月18日 下午5:22:42
 */
package cc.openhoem;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.*;


@MultipartConfig    //使用getPart()处理上传的文件
public class UploadServlet extends HttpServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Part part = req.getPart("photo");    //使用getPart()取得Part对象
        String filename = getFileName(part);
        writeTo(filename, part);
    }

    //取得上传文件名存在问题
    private String getFileName(Part part) {
        String header = part.getHeader("Content-Disposition");
        String fileName = header.substring(header.indexOf("fileName="") + 10, header.lastIndexOf("""));
        return fileName;
    }

    //存储文件
    private void writeTo(String fileName, Part part) throws IOException, FileNotFoundException{
        InputStream in =part.getInputStream();
        OutputStream out = new FileOutputStream("D:/ProgramData/" + fileName);
        byte[] buffer = new byte[1024];
        int length = -1;
        while (-1 != (length = in.read(buffer))) {
            out.write(buffer, 0, length);
        }
        out.close();
        in.close();
    }
}

 @MultipartConfig标注可用来设置Servlet处理上传文件的相关信息,属性如下:

location:字符串设置,设置写入文件时的目录,如果设置这个属性,则缓存文件就是写到指定的目录,也可以搭配Part的write()方法使用,默认为空字符串。

maxFileSize:限制上传文件大小,默认值为-1L,表示不限制大小。

maxRequestSize:限制multipart/form-data请求个数,默认值为-1L,表示不限制个数。

fileSizeThreshold:整数值设置,若上传文件超过设置门槛,会写入缓存文件,默认值为0。

multipart/form-data发送的每个内容区段,都会有以下的标头信息:

Content-Disposition:form-data;name="fileName";fileName="caterpillar.jpg"

Content-Type:image/jpeg......

如果想取得这些标头信息,可以使用Part对象的getHeader()方法,指定标头名称来取得对应的值。所以想要取得上传文件名称,就是取得Content-Disposition标头的值,然后取得filename属性的值。最后,再利用Java I/O API写入文件中。

Part有个方便的write()方法,可以直接将上传文件指定文件名写入磁盘中,write()可指定文件名,写入的路径是相对于@MultipartConfig的location设置的路径。

多个文件上传:

<form action="UploadServlet3" method="post" enctype="multipart/form-data">
        文件1:<input type="file" name="file1" /><br><br>
        文件2:<input type="file" name="file2" /><br><br>
        文件3:<input type="file" name="file3" /><br><br>
        <input type="submit" value="上传" name="upload">
    </form>
/**
 * @Title: UploadServlet.java
 * @Package cc.openhome
 * @Description: TODO(文件上传)
 * @author rocky
 * @date 2016年4月18日 下午5:22:42
 */
package cc.openhoem;

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

@MultipartConfig(location = "D:/ProgramData")    //使用getPart()处理上传的文件
@WebServlet("/upload3.do")
public class UploadServlet3 extends HttpServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        for (Part part : req.getParts()) {  //迭代Collection中所有Part对象
            if (part.getName().startsWith("file")) { //只处理上传文件区段
                String fileName = getFileName(part);
                part.write(fileName);
            }
        }
    }

    //取得上传文件名
    private String getFileName(Part part) {
        String header = part.getHeader("Content-Disposition");
        String fileName = header.substring(header.indexOf("fileName="") + 10, header.lastIndexOf("""));
        return fileName;
    }
}
<!--web.xml-->
    <servlet>
        <servlet-name>UploadServlet3</servlet-name>
        <servlet-class>cc.openhoem.UploadServlet3</servlet-class>
        <!--设置路径-->
        <multipart-config>
            <location>D:/ProgramData</location>
        </multipart-config>
    </servlet>

使用RequestDispatcher调派请求

在Web应用程序中,经常需要多个Servlet来完成请求。例如,将另一个Servlet的请求处理流程包含(Include)进来,或将请求转发(Forward)给别的Servlet处理。如果有这类的请求,可以使用HttpServletRequest的getRequestDispatcher()方法取得RequestDispatcher接口的实现对象实例,调用时指定转发或包含的相对URL网址。

RequestDispatcher dispatcher = req.getRequestDispatcher("upload.do");

1、使用include()方法

可以将另一个Servlet的操作流程包括至目前Servlet操作流程中

package cc.openhoem;

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;
import java.io.PrintWriter;

/**
 * Created by Administrator on 2016/4/19.
 */
@WebServlet("/some.view")
public class Some extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter out = resp.getWriter();
        out.println("Some do one...");
        RequestDispatcher dispatcher = req.getRequestDispatcher("other.view?data=123456");
        dispatcher.include(req, resp);
        out.println("Some do two...");
        out.close();
    }
}

2、请求范围属性

在include()或forword()时包括请求参数的做法,仅适用于传递字符串给另一个Servlet,在调派请求的过程中,如果有必须共享的“对象”,可以设置给请求对象称为属性,称为请求范围属性(Request Scope Attribute)。

setAttribute():指定名称与对象设置属性

getAttribute():指定名称取得属性

getAttributeNames():取得所有属性名称

 

3、使用forward()方法

调用时同样传入请求与响应对象,这表示你要将请求处理转发给别的Servlet,“对客户端的响应同时也转发给另一个Servlet”

package cc.openhoem;

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.io.PrintWriter;

/**
 * Created by Administrator on 2016/4/20.
 */
@WebServlet("/helloController.do")
public class HelloController extends HttpServlet{
    private HelloModel model = new HelloModel();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //收集请求参数
        String name = req.getParameter("user");
        //PrintWriter out = resp.getWriter();
        //out.print(name);
        //委托HelloModel对象处理
        String message = model.doHello(name);
        //将结果信息设置至请求对象成为属性
        req.setAttribute("message", message);
        //转发给hello.view进行响应
        req.getRequestDispatcher("hello.view").forward(req, resp);
    }
}
package cc.openhoem;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by Administrator on 2016/4/20.
 */
public class HelloModel {
    private Map<String, String> messages= new HashMap<String, String>();

    public HelloModel() {
        messages.put("caterpillar", "Hello");
        messages.put("Justin", "Welcome");
        messages.put("momor", "Hi");
    }
    public String doHello(String user) {
        //取值
        String message = messages.get(user);
        return message + ", " + user + "!";
    }
}
package cc.openhoem;

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;

/**
 * Created by Administrator on 2016/4/20.
 */
@WebServlet("/hello.view")
public class HelloView extends HttpServlet{
    private String htmlTemplate =
            "<html>"
            +"  <head>"
            +"      <meta http-equiv='Content-Type'"
            +"          content='text/html; charset=UTF-8'"
            +"      <title>%s</title>"
            +"  <body>"
            +"      <h1>%s</h1>"
            +"  </body>"
            +"</html>";
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //取得请求参数
        String user = req.getParameter("user");
        //取得请求属性
        String message = (String) req.getAttribute("message");
        //产生HTML结果
        String html = String.format(htmlTemplate, user, message);
        //输出HTML结果
        resp.getWriter().print(html);
    }
}
  • 3.3 关于HttpServletResponse

可以使用HttpServletResponse来对浏览器进行响应。大部分情况使用setContentType()设置响应类型,使用getWriter()取得PrintWriter对象,然后使用PrintWriter的println()等方法输出HTML内容。

还可以进一步使用setHeader()、addHeader()等方法进行响应标头的设置,或者是使用sendRedirect()、sendError()方法,对客户端要求重定向网页,或是传送错误状态信息。若必要,也可以使用getOutputStream()取得ServletOutputStream,直接使用串流对象对浏览器进行字节数据的响应。

设置响应标头、缓冲区

setHeader()、addHeader()来设置相应标头,setHeader()设置标头名称与值,addHeader()则可以在同一个标头名称上附加值。如果标头的值是整数,则可以使用setIntHeader()、addIntHeader()方法,如果标头的值是日期,则使用setDateHeader()、addDateHeader()方法。

容器可以对响应内容进行缓冲,通常默认缓冲。缓冲方法:

getBufferSize()、setBufferSize()、isCommited()、reset()、resetBuffer()、flushBuffer()

在缓冲区未满之前,设置的响应相关内容都不会真正传至客户端,可以使用isCommitted()看看是否响应已确认。如果想要重置所有响应内容,但不会清除已设置的标头内容。

flushBuffer()会清楚(flush)所有缓冲区中已设置的响应信息至客户端,reset()、resetBuffer()必须在未响应前调用。

使用getWriter()输出字符

如果要对浏览器输出HTML,在先前的范例中都会通过HttpServletResponse的getWriter()取得PrintWriter对象,然后指定字符串进行输出。

PrintWriter out = resp.getWriter();
out.println("<html>");
out.println("<head>");

1、设置Locale

浏览器如果有发送Accept-Language标头,则可以使用HttpServletRequest的getLocale()来取得一个locale对象,代表客户端可接受的语系。

可以使用HttpServletResponse的setLocale()来设置地区(locale)信息,地区信息就包括了语系与编码信息。语系信息通常通过响应标头Content-Language来设置,而setLocale()也会设置HTTP响应的Content-Language标头

resp.setLocale(Locale.TAIWAN);

这会将HTTP响应的Content-Language设置为zh-TW,而字符编码处理设置为BIG5.可以使用HttpServletResponse的getCharacterEncoding()方法取得编码设置。

   <!--web.xml-->
   <locale-encoding-mapping-list>
        <locale-encoding-mapping>
            <locale>zh_TW</locale>
            <encoding>UTF-8</encoding>
        </locale-encoding-mapping>
    </locale-encoding-mapping-list>

2、使用setCharacterEncoding()或setContentType()

也可以调用HttpServletResponse的setCharacterEncoding()设置字符编码:

resp.setCharacterEncoding("UTF-8");

或者在使用HttpServletResponse的setContentType()时,指定charset,charset的值会自动用来调用setCharacterEncoding()。例如,以下不仅设置内容类型为text/html,也会自动调用setCharacterEncoding(),设置编码为UTF-8:

resp.setContentType("text/html; charset=UTF-8");

如果使用了setCharacterEncoding()或setContentType()时指定了charset,则setLocale()就会被忽略。

因为浏览器需要知道如何处理你的响应,所以必须告知内容类型,setContentType()方法在响应中设置content-type响应标头,你只要指定MIME(multipurpose Internet Mail Extensions)类型就可以了。由于编码涉资与内容类型通常都要设置,所以调用setContentType()设置内容类型时,同时指定charset属性是个方便且常见的做法。

常见的设置有text/html、application/pdf、application/jar、application/x-zip、image/jpeg等。

    <mime-mapping>
        <!--设置文件后缀-->
        <extension>pdf</extension>
        <!--设置对应的MIME类型名称-->
        <mime-type>application/pdf</mime-type>
    </mime-mapping>

发送中文请求参数值,Servlet可正确接收处理并显示在浏览器中。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>宠物类型大调查</title>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
</head>
<body>
    <form action="pet.do" method="post">
        姓名:<input type="text" name="user"><br>
        邮件:<input type="text" name="email"><br>
        你喜爱的宠物代表:<br>
        <select name="type" size="6" multiple="true">
            <option value="猫"></option>
            <option value="狗"></option>
            <option value="鱼"></option>
            <option value="鸟"></option>
        </select><br>
        <input type="submit" value="送出">
    </form>
</body>
</html>
package cc.openhoem;

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.io.PrintWriter;

/**
 * Created by Administrator on 2016/4/20.
 */
@WebServlet("/pet.do")
public class Pet extends HttpServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置请求对象字符编码
        req.setCharacterEncoding("UTF-8");
        //设置内容类型
        resp.setContentType("text/html; cahrset=UTF-8");
        //取得输出对象
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>感谢填写</title>");
        //取得请求参数值
        out.println("联系人:<a href='mailto:"+req.getParameter("email") + "'>" +
        req.getParameter("user") + "</a>");
        out.println("<br>喜爱的宠物类型");
        out.println("<ul>");
        //取得复选项请求参数值
        for (String type : req.getParameterValues("type")) {
            out.println("<li>" + type + "</li>");
        }
        out.println("</ul>");
        out.println("</body>");
        out.println("</html>");
        out.close();
    }
}

使用getOutputStream()输出二进制字符

在大部分情况下,会从HttpServletResponse取得PrintWriter实例,使用println()对浏览器进行字符输出。然而有时候需要直接对浏览器进行字节输出,这时可以使用HttpServletResponse的gteOutputStream()方法取得ServletOutputStream实例。

package cc.openhoem;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Created by Administrator on 2016/4/20.
 */
public class DownLoad extends HttpServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String passwd = req.getParameter("passwd");
        if ("123456".equals(passwd)) {
            //设置内容类型
            resp.setContentType("application/pdf");
            //取得输入串流
            InputStream in = getServletContext().getResourceAsStream("/WEB-INF/jdbc.pdf");
            //取得输出串流
            OutputStream out = resp.getOutputStream();
            //读取PDF并输出至浏览器
            writeBytes(in, out);
        }
    }

    private void writeBytes(InputStream in, OutputStream out) throws IOException {
        byte[] buffer = new byte[1024];
        int length = -1;
        while(-1 != (length = in.read(buffer))) {
            out.write(buffer, 0, length);
        }
        in.close();
        out.close();
    }
}

使用sendRedirect()、sendError()

forward()会将请求转发至指定的URL,这个动作是在Web容器中进行,浏览器并不会知道请求被转发,地址栏也不会有变化。

在转发过程中,都还是在同一个请求周期,这也是为什么RequestDispatcher是由调用HttpServletRequest的getRequestDispatcher()方法取得,所以在HttpServletRequest中使用setAttribute()设置的属性对象,都可以在转发过程中共享。

可以使用HttpServletResponse的sendRedirect()要求浏览器重新请求另一个URL,又称为重定向(Redirect),使用时可指定绝对URL或相对URL。

reponse.sendRedirect("http://openhoem.cc");

这个方法会在响应中设置HTTP状态码301以及Location标头,浏览器接收到这个标头,会重新使用GET方法请求指定URL,因此,地址栏上会发生URL变更。

如果在处理请求的过程中发现一些错误,而你想要传送服务器默认的状态与错误信息,可以使用sendError()方法。例如,如果根据请求参数必须返回的资源根本不存在,则可以发出错误信息:

resp.sendError(HttpServletResponse.SC_NOT_FOUND, "笔记文件");
  • 3.4 综合练习

微博应用程序功能概述

微博首页、微博会员注册、会员注册失败画面、会员注册成功画面、会员登陆成功画面

实现会员注册功能

<form>标签

<form method="post" action="register.do">

邮件地址字段

<input type="text" name="username" size="25" maxlength="100">

名称字段

<input type="text" name="username" size="25" maxlength="16">

密码与确认密码字段

<input type="password" name="password" size="25" maxlength="16">

<input type="password" name="confirmedPasswd" size="25" maxlength="16">

package cc.openhoem.controller;

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.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2016/4/21.
 */
@WebServlet("/register.do")
public class Register extends HttpServlet{
    private final String USERS = "D:/ProgramData";
    private final String SUCCESS_VIEW = "success.view";
    private final String ERROR_VIEW = "error.view";

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //取得请求参数
        String email = req.getParameter("emial");
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String confirmPasswd = req.getParameter("confirmPasswd");

        List<String> errors = new ArrayList<String>();
        if (isInvalidEmail(email)) {
            errors.add("未填写邮件或邮件格式不正确");
        }
        if (isInvalidUsername(username)) {
            errors.add("用户名称为空或已存在");
        }
        if (isInvalidPassword(password, confirmPasswd)) {
            errors.add("请确认莫玛符合格式并再次确认密码");
        }
        String resultPage = ERROR_VIEW;
        //窗体验证出错误,设置收集错误的List为请求属性
        if (!errors.isEmpty()) {
            req.setAttribute("errors", errors);
        } else {
            resultPage = SUCCESS_VIEW;
            //创建用户资料
            createUserData(email, username, password);
        }

        req.getRequestDispatcher(resultPage).forward(req, resp);
    }

    private boolean isInvalidEmail(String email) {
        return email == null || !email.matches(
                "^[_a-z0-9-]+([.]" + "[_a-z0-9-]+)*@[a-z0-9-]+([.][a-z0-9-]+)*$");
    }
    //检查用户资料夹是否创建来确认用户是否已注册
    private boolean isInvalidUsername(String username) {
        for (String file : new File(USERS).list()) {
            if (file.equals(username)) {
                return true;
            }
        }
        return false;
    }
    private boolean isInvalidPassword(String password, String confirmedPasswd) {
        return password == null ||
                password.length() < 6 ||
                password.length() > 16 ||
                !password.equals(confirmedPasswd);
    }
    //创建用户资料,在profile中存储邮件与密码
    private void createUserData(String email, String username, String password) throws IOException {
        File userhome = new File(USERS + "/" +username);
        userhome.mkdir();
        BufferedWriter writer = new BufferedWriter(new FileWriter(userhome + "/profile"));
        writer.write(email + "	" + password);
        writer.close();
    }
}
package cc.openhoem.controller;

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.io.PrintWriter;
import java.util.List;

/**
 * Created by Administrator on 2016/4/21.
 */
@WebServlet("/error.view")
public class Error extends HttpServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置响应编码
        resp.setContentType("text/html; charset=UTF-8");
        PrintWriter out = resp.getWriter();
        out.println("<!DOCTYPE HTML PUBLIC" + "'-//W3C//DTD HTML 4.01 Transitional//EN>'");
        out.println("<html>");
        out.println("<head>");
        out.println("<meta http-equiv='Content-Type'" + "content='text/html;charset=UTF-8'>");
        out.println("   <title>新增会员失败</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>新增会员失败</h1>");
        out.println("u1 style='color: rgb(255, 0, 0);'>");
        //取得请求属性
        List<String> errors = (List<String>) req.getAttribute("errors");
        //显示错误信息
        for (String error : errors) {
            out.println("   <li>" + error + "</li>");
        }
        out.println("</u1>");
        out.println("<a href='register.html'>返回注册页面</a>");
        out.println("</body>");
        out.println("</html>");
        out.close();
    }
}

要注意到,为了显示中文的错误信息,使用HttpServletResponse的setContentType()时顺便指定了charset属性,由于只有在失败时才会转发到这个页面,并在请求中带有error属性,于是使用HttpServletRequest的getAtrribute()取得属性,并逐一显示错误信息。

package cc.openhoem.controller;

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.io.PrintWriter;

/**
 * Created by Administrator on 2016/4/21.
 */
@WebServlet("/success.view")
public class Success extends HttpServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        //....
        out.println("<!DOCTYPE HTML PUBLIC" + "'-//W3C//DTD HTML 4.01 Transitional//EN>'");
        out.println("<html>");
        out.println("<head>");
        out.println("<meta http-equiv='Content-Type'" + "content='text/html;charset=UTF-8'>");
        out.println("   <title>新增会员失败</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>会员 " + req.getParameter("username") + " 注册成功</h1>");
        out.println("<a href='index.html'>回首页登录</a>");
        out.println("</body>");
        out.println("</html>");
        out.close();
    }
}

实现会员登录功能

<form>标签

<form method="post" action="login.do">

名称字段

<input type="text" name="username">

密码字段

<input type="password" name="password">

package cc.openhoem.controller;

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.*;

/**
 * Created by Administrator on 2016/4/21.
 */
@WebServlet("/login.do")
public class Login extends HttpServlet{
    private final String USERS = "D:/ProgramData";
    private final String SUCCESS_VIEW = "member.view";
    private final String ERROR_VIEW = "index.html";

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");

    }
    
    private boolean cheakLogin(String username, String password) throws IOException {
        if (username != null && password != null) {
            //读取用户资料夹中的profile
            for (String file : new File(USERS).list()) {
                if (file.equals(username)) {
                    BufferedReader reader = new BufferedReader(
                            new FileReader(USERS + "/" + file + "/profile"));
                    String passwd = reader.readLine().split("	")[1];
                    if (password.equals(password)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}

检查登录基本上就是查看用户名是否有对应的资料夹,并且看看profile文件中存放的密码是否符合,注意先前创建profile时,邮件与密码中间是用“ ”字符分隔。如果名称与密码不符就重新定向回首页,让用户可以重新登录,登陆信息正确就转发会员网页。

package cc.openhoem.controller;

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.io.PrintWriter;

/**
 * Created by Administrator on 2016/4/21.
 */
@WebServlet("/member.view")
public class Member extends HttpServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter out = resp.getWriter();
        out.println("<!DOCTYPE HTML PUBLIC" + "'-//W3C//DTD HTML 4.01 Transitional//EN>'");
        out.println("<html>");
        out.println("<head>");
        out.println("<meta http-equiv='Content-Type'" + "content='text/html;charset=UTF-8'>");
        out.println("   <title>新增会员失败</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>会员 " + req.getParameter("username") + " 您好!</h1>");
        out.println("</body>");
        out.println("</html>");
        out.close();
    }
}
原文地址:https://www.cnblogs.com/beaconSky/p/5370499.html