Jade Dungeon

JSP / Servlet

HTTP状态

状态码

代码 消息 描述
100 Continue 只有一个请求的一部分已经由服务器接收,但只要它并没有被拒绝,客户端应继续与该请求
101 Switching Protocols 服务器切换协议。
200 OK 请求成功
201 Created 该请求是完整的,并创建一个新的资源(比如新增加一条记录到数据库操作成功时)
202 Accepted 该请求被接受处理,但是该处理是不完整的。
203 Non-authoritative Information  
204 No Content 资源存在,但内容为空
205 Reset Content  
206 Partial Content  
300 Multiple Choices 链接列表。用户可以选择一个链接,进入到该位置。最多五个地址.
301 Moved Permanently 所请求的资源已经转移到一个新的URL
302 Found 请求的资源已经临时切换到一个新的URL(客户端自动跳转)
303 See Other 所请求的资源下,可以找到一个不同的URL(比如,负载均衡)
304 Not Modified 资源没有变化,(客户端可以直接用缓存)
305 Use Proxy  
306 Unused 在以前的版本中使用此代码。它不再使用,但被保留的代码。
307 Temporary Redirect 临时切换到一个新的URL请求的资源。
400 Bad Request 服务器不理解请求(比如请求的参数不对)
401 Unauthorized 所请求的资源需要一个用户名和密码
402 Payment Required 您不能使用此代码
403 Forbidden 被禁止访问所请求的资源
404 Not Found 服务器无法找到所请求的资源。
405 Method Not Allowed 在请求中指定的方法是不允许的。
406 Not Acceptable 服务器不支持指定的表示方式。(如请求头Accept:text/xml,而服务器程序只返回JSON)
407 Proxy Authentication Required 您必须使用代理服务器的验证,在此之前请求送达。
408 Request Timeout 请求需要较长的时间比服务器做好等待的准备。
409 Conflict 请求无法完成,因为冲突。
410 Gone 所请求的资源不再可用。
411 Length Required 没有被定义的「内容长度」。服务器将不接受该请求。
412 Precondition Failed 给出的先决条件评估为false的服务器的请求。
413 Request Entity Too Large 该服务器将不接受该请求,因为请求实体过大。
414 Request-url Too Long 服务器将不接受该请求,因为URL太长。当你转换一个「post」请求一个长的查询信息到一个「get」请求时发生。
415 Unsupported Media Type 该服务器将不接受该请求,因为不支持的媒体类型。
417 Expectation Failed  
500 Internal Server Error 未完成的请求。服务器遇到了一个意外的情况(通常为程序执行出错)
501 Not Implemented 未完成的请求。服务器不支持所需的功能。
502 Bad Gateway 未完成的请求。服务器收到无效响应从上游服务器
503 Service Unavailable 未完成的请求。服务器暂时超载或死机。
504 Gateway Timeout 网关超时。
505 HTTP Version Not Supported 服务器不支持「HTTP协议」的版本。

HttpServletResponse中指定状态码

setStatus

public void setStatus (int statusCode)

该方法设置一个任意的状态码。setStatus方法接受一个int(状态码)作为参数。 如果您的反应包含了一个特殊的状态码和文档, 请确保在使用PrintWriter实际返回任何内容之前调用setStatus

sendRedirect

public void sendRedirect(String url)

该方法生成一个302响应,连同一个带有新文档URL的Location头。

sendError

public void sendError(int code, String message)

该方法发送一个状态码(通常为 404), 连同一个在HTML文档内部自动格式化并发送到客户端的短消息。

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;

public class showError extends HttpServlet {
 
  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
            throws ServletException, IOException
  {
      response.sendError(404, "Not Funded!!!" );
  }
}

外部的Servet 容器会把message的内容简单包装为html页面。

异步Servlet

理解异步Servlet之前,让我们试着理解为什么需要它。假设我们有一个Servlet需要很多的 时间来处理,类似下面的内容:

package com.journaldev.servlet;
 
import java.io.IOException;
import java.io.PrintWriter;
 
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("/LongRunningServlet")
public class LongRunningServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
 
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        System.out.println("LongRunningServlet Start::Name="
                + Thread.currentThread().getName() + "::ID="
                + Thread.currentThread().getId());
 
        String time = request.getParameter("time");
        int secs = Integer.valueOf(time);
        // max 10 seconds
        if (secs > 10000)
            secs = 10000;
 
        longProcessing(secs);
 
        PrintWriter out = response.getWriter();
        long endTime = System.currentTimeMillis();
        out.write("Processing done for " + secs + " milliseconds!!");
        System.out.println("LongRunningServlet Start::Name="
                + Thread.currentThread().getName() + "::ID="
                + Thread.currentThread().getId() + "::Time Taken="
                + (endTime - startTime) + " ms.");
    }
 
    private void longProcessing(int secs) {
        // wait for given time before finishing
        try {
            Thread.sleep(secs);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
}

如果我们的URL是:http://localhost:8080/AsyncServletExample/LongRunningServlet?time=8000

得到响应为"Processing done for 8000 milliseconds! !"。

现在,如果你会查看服务器日志,会得到以下记录:

LongRunningServlet Start::Name=http-bio-8080-exec-34::ID=103
LongRunningServlet Start::Name=http-bio-8080-exec-34::ID=103::Time Taken=8002 ms.

所以Servlet线程实际运行超过8秒,尽管大多数时间用来处理其它Servlet请求或响应。

这可能导致线程饥饿——因为我们的Servlet线程被阻塞,直到所有的处理完成。如果服务器 的请求得到了很多过程,它将达到最大Servlet线程限制和进一步的请求会拒绝连接错误。

Servlet 3.0之前,这些长期运行的线程容器特定的解决方案,我们可以产生一个单独的 工作线程完成耗时的任务,然后返回响应客户。Servlet线程返回Servlet池后启动工作线程 。Tomcat 的 Comet、WebLogic FutureResponseServlet 和 WebSphere Asynchronous Request Dispatcher都是实现异步处理的很好示例。

容器特定解决方案的问题在于,在不改变应用程序代码时不能移动到其他Servlet容器。 这就是为什么在Servlet3.0提供标准的方式异步处理Servlet的同时增加异步Servlet支持。

实现异步Servlet

让我们看看步骤来实现异步Servlet,然后我们将提供异步支持Servlet上面的例子:

  1. 首先Servlet,我们提供异步支持 Annotation @WebServlet 的属性asyncSupported 值为true。
  2. 由于实际实现委托给另一个线程,我们应该有一个线程池实现。我们可以一个通过 Executors framework 创建线程池和使用servlet context listener来初始化线程池。
  3. 通过ServletRequest.startAsync方法获取AsyncContext的实例。AsyncContext提供方法 让ServletRequest和ServletResponse对象引用。它还提供了使用调度方法将请求转发到 另一个 dispatch() 方法。
  4. 编写一个可运行的实现,我们将进行重处理,然后使用AsyncContext对象发送请求到 另一个资源或使用ServletResponse编写响应对象。一旦处理完成,我们通过 AsyncContext.complete()方法通知容器异步处理完成。
  5. 添加AsyncListener实现AsyncContext对象实现回调方法,我们可以使用它来提供错误 响应客户端装进箱的错误或超时,而异步线程处理。在这里我们也可以做一些清理工作。

一旦我们将完成我们的项目对于异步Servlet示例,项目结构看起来会像下面的图片:

+ src
	+ com.journaldev.servlet
		LongRunningServlet.java
	+ com.journaldev.servlet.async
		AppAsyncListener.java
		AppcontextListener.java
		AsyncLongRunningServlet.java
		AsyncRequestProcesser.java

在监听中初始化线程池

package com.journaldev.servlet.async;
 
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
 
@WebListener
public class AppContextListener implements ServletContextListener {
 
    public void contextInitialized(ServletContextEvent servletContextEvent) {
 
        // create the thread pool
        ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L,
                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100));
        servletContextEvent.getServletContext().setAttribute("executor",
                executor);
 
    }
 
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        ThreadPoolExecutor executor = (ThreadPoolExecutor) servletContextEvent
                .getServletContext().getAttribute("executor");
        executor.shutdown();
    }
 
}

实现很直接,如果你不熟悉ThreadPoolExecutor 框架请读线程池的ThreadPoolExecutor

关于listeners 的更多细节,请阅读教程Servlet Listener

工作线程实现

package com.journaldev.servlet.async;
 
import java.io.IOException;
import java.io.PrintWriter;
 
import javax.servlet.AsyncContext;
 
public class AsyncRequestProcessor implements Runnable {
 
    private AsyncContext asyncContext;
    private int secs;
 
    public AsyncRequestProcessor() {
    }
 
    public AsyncRequestProcessor(AsyncContext asyncCtx, int secs) {
        this.asyncContext = asyncCtx;
        this.secs = secs;
    }
 
    @Override
    public void run() {
        System.out.println("Async Supported? "
                + asyncContext.getRequest().isAsyncSupported());
        longProcessing(secs);
        try {
            PrintWriter out = asyncContext.getResponse().getWriter();
            out.write("Processing done for " + secs + " milliseconds!!");
        } catch (IOException e) {
            e.printStackTrace();
        }
        //complete the processing
        asyncContext.complete();
    }
 
    private void longProcessing(int secs) {
        // wait for given time before finishing
        try {
            Thread.sleep(secs);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

注意:在请求和响应时使用AsyncContext对象,然后在完成时调用

AsyncListener实现

asyncContext.complete()方法。

package com.journaldev.servlet.async;
 
import java.io.IOException;
import java.io.PrintWriter;
 
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebListener;
 
@WebListener
public class AppAsyncListener implements AsyncListener {
 
    @Override
    public void onComplete(AsyncEvent asyncEvent) throws IOException {
        System.out.println("AppAsyncListener onComplete");
        // we can do resource cleanup activity here
    }
 
    @Override
    public void onError(AsyncEvent asyncEvent) throws IOException {
        System.out.println("AppAsyncListener onError");
        //we can return error response to client
    }
 
    @Override
    public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
        System.out.println("AppAsyncListener onStartAsync");
        //we can log the event here
    }
 
    @Override
    public void onTimeout(AsyncEvent asyncEvent) throws IOException {
        System.out.println("AppAsyncListener onTimeout");
        //we can send appropriate response to client
        ServletResponse response = asyncEvent.getAsyncContext().getResponse();
        PrintWriter out = response.getWriter();
        out.write("TimeOut Error in Processing");
    }
 
}

通知的实现在 Timeout()方法,通过它发送超时响应给客户端。

Async Servlet 实现

这是我们的异步Servlet实现,注意使用AsyncContext和ThreadPoolExecutor进行处理。

package com.journaldev.servlet.async;
 
import java.io.IOException;
import java.util.concurrent.ThreadPoolExecutor;
 
import javax.servlet.AsyncContext;
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(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true)
public class AsyncLongRunningServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
 
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        System.out.println("AsyncLongRunningServlet Start::Name="
                + Thread.currentThread().getName() + "::ID="
                + Thread.currentThread().getId());
 
        request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
 
        String time = request.getParameter("time");
        int secs = Integer.valueOf(time);
        // max 10 seconds
        if (secs > 10000)
            secs = 10000;
 
        AsyncContext asyncCtx = request.startAsync();
        asyncCtx.addListener(new AppAsyncListener());
        asyncCtx.setTimeout(9000);
 
        ThreadPoolExecutor executor = (ThreadPoolExecutor) request
                .getServletContext().getAttribute("executor");
 
        executor.execute(new AsyncRequestProcessor(asyncCtx, secs));
        long endTime = System.currentTimeMillis();
        System.out.println("AsyncLongRunningServlet End::Name="
                + Thread.currentThread().getName() + "::ID="
                + Thread.currentThread().getId() + "::Time Taken="
                + (endTime - startTime) + " ms.");
    }
 
}

Run Async Servlet

现在,当我们将上面运行servlet URL:

http://localhost:8080/AsyncServletExample/AsyncLongRunningServlet?time=8000

得到响应和日志:

AsyncLongRunningServlet Start::Name=http-bio-8080-exec-50::ID=124
AsyncLongRunningServlet End::Name=http-bio-8080-exec-50::ID=124::Time Taken=1 ms.
Async Supported? true
AppAsyncListener onComplete

如果运行时设置time=9999,在客户端超时以后会得到响应超时错误处理和日志:

AsyncLongRunningServlet Start::Name=http-bio-8080-exec-44::ID=117
AsyncLongRunningServlet End::Name=http-bio-8080-exec-44::ID=117::Time Taken=1 ms.
Async Supported? true
AppAsyncListener onTimeout
AppAsyncListener onError
AppAsyncListener onComplete
Exception in thread "pool-5-thread-6" java.lang.IllegalStateException: The request associated with the AsyncContext has already completed processing.
    at org.apache.catalina.core.AsyncContextImpl.check(AsyncContextImpl.java:439)
    at org.apache.catalina.core.AsyncContextImpl.getResponse(AsyncContextImpl.java:197)
    at com.journaldev.servlet.async.AsyncRequestProcessor.run(AsyncRequestProcessor.java:27)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:680)

注意:Servlet线程执行完,很快就和所有主要的处理工作是发生在其他线程。

这是所有异步Servlet内容,希望你喜欢它。code/servlet/AsyncServletExample

MIME操作

格式:

Content-Type: [type]/[subtype]; parameter 

其中type有下面的形式:

Text
用于标准化地表示的文本信息,文本消息可以是多种字符集和或者多种格式的;
Multipart
用于连接消息体的多个部分构成一个消息,这些部分可以是不同类型的数据;
Application
用于传输应用程序数据或者二进制数据;
Message
用于包装一个E-mail消息;
Image
用于传输静态图片数据;
Audio
用于传输音频或者音声数据;
Video
用于传输动态影像数据,可以是与音频编辑在一起的视频数据格式。

subtype用于指定type的详细形式。content-type/subtype配对的集合和与此相关的 参数,将随着时间而增长。为了确保这些值在一个有序而且公开的状态下开发,MIME使用 Internet Assigned Numbers Authority (IANA)作为中心的注册机制来管理这些值。常用的 subtype值如下所示:

  • text/plain(纯文本 )
  • text/html(HTML文档)
  • application/xhtml+xml(XHTML文档)
  • image/gif(GIF图像)
  • image/jpeg(JPEG图像)【PHP中为:image/pjpeg】
  • image/png(PNG图像)【PHP中为:image/x-png】
  • video/mpeg(MPEG动画)
  • application/octet-stream(任意的二进制数据)
  • application/pdf(PDF文档)
  • application/msword(Microsoft Word文件)
  • message/rfc822(RFC822 形式)
  • multipart/alternative(HTML邮件的HTML形式和纯文本形式,相同内容使用不同形式表示)
  • application/x-www-form-urlencoded(使用HTTP的POST方法提交的表单)
  • multipart/form-data(同上,但主要用于表单提交时伴随文件上传的场合)

此外,尚未被接受为正式数据类型的subtype,可以使用x-开始的独立名称 (例如:application/x-gzip)。 vnd-开始的固有名称也可以使用 (例:application/vnd.ms-excel)。

parameter可以用来指定附加的信息,更多情况下是用于指定text/plaintext/htm 等的文字编码方式的charset参数。 MIME根据type制定了默认的subtype,当客户端 不能确定消息的subtype的情况下,消息被看作默认的subtype进行处理。Text默认是 text/plainApplication默认是application/octet-stream,而Multipart默认 情况下被看作multipart/mixed

Content-Disposition简介

Content-Disposition是HTTP Response Header的一个参数。但是这个不是标准参数, HTTP/1.1的规范文档中,对于这个参数的解释大意如下:

Content-Disposition参数本来是为了在客户端另存文件时提供一个建议的文件名,但是 考虑到安全的原因,就从规范中去掉了这个参数。但是由于很多浏览器已经能够支持这个 参数,所以只是在规范文档中列出,但是要注意这个不是HTTP/1.1的标准参数。

其实IE是根据Content-Dispositionfilename这个段中文件名的后缀来识别 这个文件类型的。

response.addHeader("Content-Disposition", "attachment;filename=test.xls"); 

那么,在进行Web开发时,可能遇到遇到以下几种需求:

  • 希望某类或者某已知MIME 类型的文件(比如:*.gif;.txt;.htm)能够在访问时弹出「文件下载」对话框。
  • 希望客户端下载时以指定文件名显示。
  • 希望某文件直接在浏览器上显示而不是弹出文件下载对话框。

对于上面的需求,使用Content-Disposition属性就可以解决。下面是代码示例(Java语言):

response.setHeader("Content-disposition", "attachment;filename=" + fileName);
  • Content-disposition为属性名。
  • attachment表示以附件方式下载。如果要在页面中打开,则改为inline
  • filename如果为中文,则会出现乱码。解决办法有两种:
fileName = new String(fileName.getBytes(「GBK」), "ISO-8859-1");

或:

fileName = HttpUtility.UrlEncode(filename, System.Text.Encoding.UTF8);