2016-07-27 3 views
0

저는 Jetty 9를 사용하고 있으며 모든 바디가 서버에 도착하기 전에 PUT 요청의 헤더를 처리하려고합니다. 여기에 내가 무슨 짓을했는지의 :Jetty Embedded - PUT Verb - 바디가 도착하기 전에 프로세스 헤더가 도착합니다.

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에 연결을 수신 : Server.java는

필터 HelloPrintingFilter이 실행되면 요청은 HelloServlet에 의해 처리됩니다. 대신 클라이언트는 서버에 연결하여 많은 데이터를 전송 한 다음 5 초 동안 대기하고 마지막으로 서버와의 연결을 닫습니다.

두 프로그램의 실행은 다음과 같은 결과가 산출 : 난 단지 내 필터 코드는 결국 실행 얻을 수있는 타임 스탬프를 보면

1469613527373: Hello from filter 
1469613527373: Hello from HelloServlet PUT 

:

클라이언트 :

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

서버 시체가 도착했습니다. 아무도 나에게 그걸하는 법을 설명 할 수 있을까? 일반적인 사용 사례는 다음과 같습니다. 클라이언트가 5GB 파일을 업로드하려고합니다. 헤더가 도착하자마자 Content-MD5 헤더 또는 확인해야하는 맞춤 헤더가 있는지 확인하여 확인 여부를 확인하고 싶습니다. 요청이 OK이면 본문 처리를 시작합니다. 요청이 OK가 아니면 연결을 닫으십시오.

감사합니다.

답변

0

잡았다 ! 문제는 서버 측이 아니라 클라이언트 측에서 발생했으며 스텁으로 만 의도되었습니다. 특히 문제는 HttpUrlConnection의 버퍼링 문제입니다.

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

을 내가

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

처럼 뭔가 루프를 변경하면 나는 즉시 것을 나타내는, 서버 측에서 아무것도 점점하지 OutOfMemoryError 예외가 :

client.java 불러 오기, 내가 가진 단일 바이트는 전송되지 않았습니다. 그리고 물론 그것은 옳다. 왜냐하면 머리말을 전선에 올리기 전에 HttpUrlConnectionContent-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); 
     } 
    } 
} 
+0

잘못된 가정. [청크 분할 전송 인코딩] (https://en.wikipedia.org/wiki/Chunked_transfer_encoding)이라고하는 'Content-Length' 헤더없이 요청 및/또는 응답 본문 내용을 전송하는 것은 완전히 유효합니다. Jetty는 클라이언트 및/또는 서버로 구입 즉시 사용할 수 있습니다. –

0

여러 요청을 사용하십시오. 예 : 첫 번째 요청에는 맞춤 헤더가 포함되며 후속 요청은 5GB 파일 업로드에 사용됩니다.

+0

죄송합니다,이 옵션을 선택하지 않습니다. "다른"클라이언트 구현에 대한 제어가 없기 때문에 서버 측의 각 요청마다이 작업을 수행해야합니다. – xmas79

+0

HTTP는 TCP를 기반으로합니다. 클라이언트가 http 연결을 닫지 않고 시간 초과가 트리거되고 요청의 데이터 크기가 충분하면 서버가 HTTP 헤더와 같은 부분 데이터를 수신합니다. HTTP 요청을 구문 분석합니다. 괜찮아. 이제 헤더를 확인하십시오. 거부하는 경우 클라이언트와 상관없이 헤더를 닫을 수 있습니다. – samm

+0

잘못된 것입니다. 서버 (Jetty)는 항상 부분 데이터를 가져옵니다. 문제는 부두가 헤더가 도착했을 때 또는 몸이 아직 도착한 상태에서 5k 또는 5MB 또는 5GB 일 때 요청 헤더를 확인하는 옵션을 제공하지 않는 것 같습니다. 필터 코드를 실행하기 전에 모든 데이터를 처리합니다. 그러나 실제로 서버가 약 1k (헤더) 이후에 연결을 끊을 수 있다는 것을 이미 알고 있다면 서버가 5GB의 데이터를 섭취해서는 안됩니다. – xmas79

0

HelloServlet.doPut()에서 아무것도하지 않으므로 기본적으로 Servlet 컨테이너 (Jetty라고 함)에게 요청 처리를 완료했다고 알리고 있습니다.

Jetty의 요청 처리는 네트워크의 일련의 버퍼에 의해 처리됩니다.

PUT 헤더와 본문 내용의 시작이 단일 버퍼에 맞을 가능성이 있습니다.

부두 밖으로 헤더를 구문 분석 한 다음 HelloFilter 타격, 서블릿 체인에 대한 요청의 파견을 시작, 다음 필터는 때 HelloServlet.doPut()을 시간에 chain.doFilter(request, response);

점과 체인을 따라 이동합니다 에 도달하면 헤더가 처리되고 본문 내용의 시작 부분이 doPut()에있는 구현을 기다리고 HttpServletRequest.getInputStream()을 호출하고 처리를 시작합니다. 이때 Jetty는 자유롭게 네트워크에서 더 많은 버퍼를 읽을 수 있습니다.

참고 : 서블릿 요청 입력 스트림을 읽지 않고 종료하고 응답이 Connection: close 표시하지 않은 경우는 (알려진 후, 다음 부두는 다음 요청을 찾고 완료 전체 읽기 요청을 강요 당할 것이다 HTTP/1의 경우 persistent connection입니다.1 spec)

요청 본문 내용을 거부하는 가장 가까운 목표는 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"); 
    } 
} 

이것은 HttpUrlConnection에서 버퍼링 존재하는 모든 혼란을 점점 피하기 위해 Socket 원료 및 원료 스트림을 사용

여기에 완벽한 예입니다. 당신이 행복/보통의 경우에 볼 수 있습니다

출력은 ...이처럼

416ms ---- testGoodPost() 
416ms client open connection 
2016-07-27 06:40:22.180:INFO:oejs.AbstractConnector:main: Started [email protected]{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