Jetty Embedded - PUT动词 - 在身体到达之前处理标题

时间:2016-07-27 10:07:54

标签: java jetty embedded-jetty

我正在使用Jetty 9,我试图在所有正文到达服务器之前处理PUT请求的标头。这就是我所做的:

Server.java:

public class SimplestServer
{
    public static void main(String[] args) throws Exception
    {
        Server server = new Server(9080);

        ServletHandler handler = new ServletHandler();
        server.setHandler(handler);

        handler.addServletWithMapping(HelloServlet.class, "/*");
        handler.addFilterWithMapping(HelloPrintingFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));

        server.start();
        server.dumpStdErr();
        server.join();
    }

    public static class HelloServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;

        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
            System.out.println(System.currentTimeMillis() + ": Hello from HelloServlet GET");
        }

        @Override
        protected void doPut(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
            System.out.println(System.currentTimeMillis() + ": Hello from HelloServlet PUT");
        }
    }

    public static class HelloPrintingFilter implements Filter {
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
            System.out.println(System.currentTimeMillis() + ": Hello from filter");
            chain.doFilter(request, response);
        }

        @Override
        public void init(FilterConfig arg0) throws ServletException {
            System.out.println(System.currentTimeMillis() + ": Init from filter");
        }

        @Override
        public void destroy() {
            System.out.println(System.currentTimeMillis() + ": Destroy from filter");
        }
    }
}

Client.java

public class SimplestClient
{
    public static void main(String[] args) throws Exception
    {
        URL url = new URL("http://localhost:9080/resource");
        HttpURLConnection httpCon = (HttpURLConnection) url.openConnection();
        httpCon.setDoOutput(true);
        httpCon.setRequestMethod("PUT");
        OutputStream out = httpCon.getOutputStream();
        byte[] b = new byte[65536];
        Random r = new Random();
        r.nextBytes(b);
        for (int i = 0; i < 1024; i++) {
            out.write(b);
        }
        System.out.println(System.currentTimeMillis() + ": Data sent. Waiting 5 seconds...");

        try {
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        out.close();
        System.out.println(System.currentTimeMillis() + ": Done!");
        httpCon.getInputStream();
    }
}

简而言之,服务器程序侦听端口9080上的连接,当请求到达时,过滤器HelloPrintingFilter被执行,然后请求由HelloServlet处理。相反,客户端连接到服务器,发送一堆数据,然后休眠5秒钟,最后关闭与服务器的连接。

两个程序的运行产生以下结果:

客户端:

1469613522350: Data sent. Waiting 5 seconds...
1469613527351: Done!

服务器:

1469613527373: Hello from filter
1469613527373: Hello from HelloServlet PUT

查看时间戳我只能在所有正文到达后执行我的过滤器代码。谁能解释我怎么做?典型的用例是:客户端尝试上传5GB文件。一旦标题到达,我想检查它们是否正常(例如,通过检查是否存在Content-MD5标题或我需要检查的任何自定义标题)。如果请求正常,则开始处理正文。如果请求不正确,则关闭连接。

感谢。

3 个答案:

答案 0 :(得分:0)

使用多个请求。例如第一个请求包括自定义标头,后续请求用于上传5GB文件。

答案 1 :(得分:0)

你在HelloServlet.doPut()中没有做任何事情,所以你基本上告诉Servlet容器(也就是Jetty)你已经完成了处理该请求。

Jetty中的请求处理由来自网络的一系列缓冲区处理。

您的PUT标头和身体内容的开始可能适合单个缓冲区。

Jetty将解析标题,然后开始将请求发送到Servlet链,点击HelloFilter,然后您的过滤器沿chain.doFilter(request, response);

到达HelloServlet.doPut(),标题已处理以及正文内容的开头尚未处理的时间点,等待doPut()中的实施调用HttpServletRequest.getInputStream()并开始处理它,此时Jetty可以自由地开始从网络上读取更多缓冲区。

  

注意:如果你的servlet在没有读取请求输入流的情况下退出,并且响应没有指示Connection: close,那么Jetty将被强制读取整个请求以完成查找之后的下一个请求(在HTTP / 1.1规范中称为persistent connection

您最接近达到拒绝请求正文内容的既定目标是使用您在HTTP / 1.1规范中提供的内容(假设这是HTTP / 1.1请求)。即正确的响应状态代码和服务器启动的Connection: close响应标头。

这是一个完整的例子:

package jetty;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

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

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Uptime;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class PutRejectExample
{
    public static class RejectServlet extends HttpServlet
    {
        @Override
        protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
        {
            timedLog("doPut() - enter");
            if (req.getHeader("X-Key") == null)
            {
                resp.setHeader("Connection", "close");
                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
                timedLog("doPut() - rejected");
                return;
            }

            File output = File.createTempFile("reject-", ".dat");
            try (FileOutputStream out = new FileOutputStream(output))
            {
                IO.copy(req.getInputStream(), out);
            }
            resp.setStatus(HttpServletResponse.SC_OK);
            resp.setHeader("Connection", "close"); // be a good HTTP/1.1 citizen
            timedLog("doPut() - exit");
        }
    }

    private static Server server;
    private static int port;

    private static void timedLog(String format, Object... args)
    {
        System.out.printf(Uptime.getUptime() + "ms " + format + "%n", args);
    }

    @BeforeClass
    public static void startServer() throws Exception
    {
        server = new Server();
        ServerConnector connector = new ServerConnector(server);
        connector.setPort(0);
        server.addConnector(connector);

        // collection for handlers
        HandlerCollection handlers = new HandlerCollection();
        server.setHandler(handlers);

        // servlet context
        ServletContextHandler context = new ServletContextHandler();
        context.addServlet(RejectServlet.class, "/reject");
        handlers.addHandler(context);

        // default handler
        handlers.addHandler(new DefaultHandler());

        // start server
        server.start();

        // grab port
        port = connector.getLocalPort();
    }

    @AfterClass
    public static void stopServer() throws Exception
    {
        server.stop();
    }

    private void performPUT(int requestSize, String... extraRequestHeaders) throws IOException
    {
        StringBuilder req = new StringBuilder();
        req.append("PUT /reject HTTP/1.1\r\n");
        req.append("Host: localhost:").append(port).append("\r\n");
        req.append("Content-Length: ").append(requestSize).append("\r\n");
        for (String extraHeader : extraRequestHeaders)
        {
            req.append(extraHeader);
        }
        req.append("\r\n");

        timedLog("client open connection");
        try (Socket socket = new Socket())
        {
            socket.connect(new InetSocketAddress("localhost", port));

            try (OutputStream out = socket.getOutputStream();
                 InputStream in = socket.getInputStream();
                 InputStreamReader reader = new InputStreamReader(in))
            {
                timedLog("client send request (headers + body)");
                try
                {
                    // write request line + headers
                    byte headerBytes[] = req.toString().getBytes(StandardCharsets.UTF_8);
                    out.write(headerBytes);
                    out.flush();

                    // write put body content
                    int bufSize = 65535;
                    byte[] buf = new byte[bufSize];
                    int sizeLeft = requestSize;
                    while (sizeLeft > 0)
                    {
                        int writeSize = Math.min(sizeLeft, bufSize);
                        ThreadLocalRandom.current().nextBytes(buf);
                        out.write(buf, 0, writeSize);
                        out.flush();
                        sizeLeft -= writeSize;
                        try
                        {
                            // simulate a slower connection
                            TimeUnit.MILLISECONDS.sleep(10);
                        }
                        catch (InterruptedException ignore)
                        {
                            // ignore
                        }
                    }
                }
                catch (IOException e)
                {
                    timedLog("client request send exception");
                    e.printStackTrace(System.out);
                }
                timedLog("client send request complete");

                timedLog("client read response");
                try
                {
                    StringWriter respStream = new StringWriter();
                    IO.copy(reader, respStream);

                    timedLog("client response: %s", respStream.toString());
                }
                catch (IOException e)
                {
                    timedLog("client read response exception");
                    e.printStackTrace(System.out);
                }
            }
        }
        timedLog("client connection complete");
    }

    @Test
    public void testBadPost() throws IOException
    {
        timedLog("---- testBadPost()");
        performPUT(1024 * 1024 * 10);
    }

    @Test
    public void testGoodPost() throws IOException
    {
        timedLog("---- testGoodPost()");
        performPUT(1024 * 1024 * 10, "X-Key: foo\r\n");
    }
}

这使用原始Socket和原始流来避免因HttpUrlConnection中存在的所有缓冲而感到困惑。

你会看到正常/快乐案例的输出是这样的......

416ms ---- testGoodPost()
416ms client open connection
2016-07-27 06:40:22.180:INFO:oejs.AbstractConnector:main: Started ServerConnector@55f3ddb1{HTTP/1.1,[http/1.1]}{0.0.0.0:46748}
2016-07-27 06:40:22.181:INFO:oejs.Server:main: Started @414ms
421ms client send request (headers + body)
494ms doPut() - enter
2084ms doPut() - exit
2093ms client send request complete
2093ms client read response
2094ms client response: HTTP/1.1 200 OK
Date: Wed, 27 Jul 2016 13:40:22 GMT
Connection: close
Server: Jetty(9.3.11.v20160721)
2094ms client connection complete

被拒绝案件的输出将如下所示......

2095ms ---- testBadPost()
2095ms client open connection
2096ms client send request (headers + body)
2096ms doPut() - enter
2101ms doPut() - rejected
2107ms client request send exception
java.net.SocketException: Broken pipe
    at java.net.SocketOutputStream.socketWrite0(Native Method)
    at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:109)
    at java.net.SocketOutputStream.write(SocketOutputStream.java:153)
    at jetty.PutRejectExample.performPUT(PutRejectExample.java:137)
    at jetty.PutRejectExample.testBadPost(PutRejectExample.java:180)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
2109ms client send request complete
2109ms client read response
2109ms client response: HTTP/1.1 403 Forbidden
Date: Wed, 27 Jul 2016 13:40:23 GMT
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html;charset=iso-8859-1
Content-Length: 322
Connection: close
Server: Jetty(9.3.11.v20160721)

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 403 </title>
</head>
<body>
<h2>HTTP ERROR: 403</h2>
<p>Problem accessing /reject. Reason:
<pre>    Forbidden</pre></p>
<hr /><a href="http://eclipse.org/jetty">Powered by Jetty:// 9.3.11-SNAPSHOT</a><hr/>
</body>
</html>

2109ms client connection complete

答案 2 :(得分:0)

疑难杂症!问题不是在服务器端,而是在客户端,这只是作为存根。具体来说,问题是HttpUrlConnection中的缓冲。

回顾我的client.java,我有:

    for (int i = 0; i < 1024; i++) {
        out.write(b);
    }

如果我将循环更改为

    for (int i = 0; i < 1024*1024; i++) {
        out.write(b);
    }

我立即得到OutOfMemoryError异常,在服务器端没有任何内容,表明没有传输单个字节。当然它是正确的,因为在将标题放在线上之前HttpUrlConnection需要知道体长,因为它必须发出Content-Length标题。将客户端实现更改为原始套接字,有效控制字节何时进入线路,解决了问题。

作为旁注,通过删除过滤器类可以进一步简化服务器代码。完整的服务器端代码是:

server.java:

public class SimplestServer
{
    public static void main(String[] args) throws Exception
    {
        Server server = new Server(9080);

        ServletHandler handler = new ServletHandler();
        server.setHandler(handler);

        handler.addServletWithMapping(HelloServlet.class, "/*");

        server.start();
        server.dumpStdErr();
        server.join();
    }

    public static class HelloServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;

        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
            System.out.println(System.currentTimeMillis() + ": Hello from HelloServlet GET");
        }

        @Override
        protected void doPut(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
            System.out.println(System.currentTimeMillis() + ": Hello from HelloServlet PUT");

            // Perform some checks here
            if (request.getHeader("X-Key") == null)
            {
                response.setHeader("Connection", "close");
                response.sendError(HttpServletResponse.SC_FORBIDDEN);
                System.out.println(System.currentTimeMillis() + ": Filter --> X-Key failed!");
                return;
            }

            // Everything OK! Read the stream.
            System.out.println(System.currentTimeMillis() + ": Proceded!!");
            InputStream body = request.getInputStream();
            long bytesReadSoFar = 0;
            byte[] data = new byte[65536];
            while (true) {
                int bytesRead = body.read(data);
                if (bytesRead < 0)
                    break;
                bytesReadSoFar += bytesRead;
            }
            System.out.println(System.currentTimeMillis() + ": Finished! Read " + bytesReadSoFar + " bytes.");
            response.setHeader("Connection", "close");
            response.setStatus(HttpServletResponse.SC_OK);
        }
    }
}