Вы ничего не делаете в своем HelloServlet.doPut()
, так что в основном вы сообщаете контейнеру сервлетов (aka Jetty), что вы закончили обработку этого запроса.
Обработка запросов в Jetty обрабатывается рядом буферов из сети.
Ваши заголовки PUT и начало содержимого вашего тела, вероятно, подходят в одном буфере.
Причал будет анализировать заголовки, и затем начать отправку запроса в Servlet цепи, попав ваш HelloFilter
, а затем ваш фильтр перемещает его вдоль цепи с chain.doFilter(request, response);
Момент времени, когда HelloServlet.doPut()
, заголовки обработаны, а начало содержимого тела не дожидалось вашей реализации в doPut()
, чтобы позвонить HttpServletRequest.getInputStream()
и начать ее обработку, после чего Jetty может начать читать больше буферов с сети.
Примечание: если ваш сервлет выходы без чтения входного потока запроса и ответ не указали Connection: close
, то Причал будет вынужден читать весь запрос завершение ищет следующий запрос после того, как (известно, как persistent connection в HTTP/1.1 спецификация)
Ближе всего вы достигнете своей заявленной цели, чтобы отклонить содержание тела запроса - использовать то, что у вас есть в спецификации 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 [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
Неправильные предположения. Совершенно корректно/законно отправлять содержимое запроса и/или ответа без заголовка Content-Length, который известен как [chunked transfer encoding] (https://en.wikipedia.org/wiki/Chunked_transfer_encoding), который Jetty поддерживает как клиент, так и сервер. –