public ServletResponseController( HttpServletRequest servletRequest, HttpServletResponse servletResponse, Executor executor, MetricReporter metricReporter, boolean developerMode) throws IOException { this.servletRequest = servletRequest; this.servletResponse = servletResponse; this.developerMode = developerMode; this.servletOutputStreamWriter = new ServletOutputStreamWriter(servletResponse.getOutputStream(), executor, metricReporter); }
@Override public void onWritePossible() throws IOException { synchronized (monitor) { if (state == State.FINISHED_OR_ERROR) { return; } assertStateIs(state, State.WAITING_FOR_WRITE_POSSIBLE_CALLBACK); state = State.WRITING_BUFFERS; } writeBuffersInQueueToOutputStream(); }
public void close() { close(NOOP_COMPLETION_HANDLER); }
public void sendErrorContentAndCloseAsync(ByteBuffer errorContent) { synchronized (monitor) { // Assert that no content has been written as it is too late to write error response if the response is committed. assertStateIs(state, State.NOT_STARTED); queueErrorContent_holdingLock(errorContent); state = State.WAITING_FOR_WRITE_POSSIBLE_CALLBACK; outputStream.setWriteListener(writeListener); } }
return; assertStateIs(state, State.WRITING_BUFFERS); callCompletionHandlerWhenDone(contentPart.handler, outputStream::close); setFinished(Optional.empty()); return; } else { writeBufferToOutputStream(contentPart); setFinished(Optional.of(e)); return;
/** * Async version of {@link org.eclipse.jetty.server.Response#sendError(int, String)}. */ private void sendErrorAsync(int statusCode, String reasonPhrase) { servletResponse.setHeader(HttpHeaders.Names.EXPIRES, null); servletResponse.setHeader(HttpHeaders.Names.LAST_MODIFIED, null); servletResponse.setHeader(HttpHeaders.Names.CACHE_CONTROL, null); servletResponse.setHeader(HttpHeaders.Names.CONTENT_TYPE, null); servletResponse.setHeader(HttpHeaders.Names.CONTENT_LENGTH, null); setStatus(servletResponse, statusCode, Optional.of(reasonPhrase)); // If we are allowed to have a body if (statusCode != HttpServletResponse.SC_NO_CONTENT && statusCode != HttpServletResponse.SC_NOT_MODIFIED && statusCode != HttpServletResponse.SC_PARTIAL_CONTENT && statusCode >= HttpServletResponse.SC_OK) { servletResponse.setHeader(HttpHeaders.Names.CACHE_CONTROL, "must-revalidate,no-cache,no-store"); servletResponse.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString()); byte[] errorContent = errorResponseContentCreator .createErrorContent(servletRequest.getRequestURI(), statusCode, Optional.ofNullable(reasonPhrase)); servletResponse.setContentLength(errorContent.length); servletOutputStreamWriter.sendErrorContentAndCloseAsync(ByteBuffer.wrap(errorContent)); } else { servletResponse.setContentLength(0); servletOutputStreamWriter.close(); } }
public void trySendError(Throwable t) { final boolean responseWasCommitted; try { synchronized (monitor) { String reasonPhrase = getReasonPhrase(t, developerMode); int statusCode = getStatusCode(t); responseWasCommitted = responseCommitted; if (!responseCommitted) { responseCommitted = true; sendErrorAsync(statusCode, reasonPhrase); } } } catch (Throwable e) { servletOutputStreamWriter.fail(t); return; } //Must be evaluated after state transition for test purposes(See ConformanceTestException) //Done outside the monitor since it causes a callback in tests. if (responseWasCommitted) { RuntimeException exceptionWithStackTrace = new RuntimeException(t); log.log(Level.FINE, "Response already committed, can't change response code", exceptionWithStackTrace); // TODO: should always have failed here, but that breaks test assumptions. Doing soft close instead. //assert !Thread.holdsLock(monitor); //servletOutputStreamWriter.fail(t); servletOutputStreamWriter.close(); } }
private void failAllParts_holdingLock(Throwable e) { assert Thread.holdsLock(monitor); ArrayList<ResponseContentPart> failedParts = new ArrayList<>(responseContentQueue); responseContentQueue.clear(); @SuppressWarnings("ThrowableInstanceNeverThrown") RuntimeException failReason = new RuntimeException("Failing due to earlier ServletOutputStream write failure", e); Consumer<ResponseContentPart> failCompletionHandler = responseContentPart -> runCompletionHandler_logOnExceptions( () -> responseContentPart.handler.failed(failReason)); executor.execute( () -> failedParts.forEach(failCompletionHandler)); }
@Override public void onError(Throwable t) { setFinished(Optional.of(t)); } };
private void setFinished(Optional<Throwable> e) { synchronized (monitor) { state = State.FINISHED_OR_ERROR; if (!responseContentQueue.isEmpty()) { failAllParts_holdingLock(e.orElse(new IllegalStateException("ContentChannel closed."))); } } assert !Thread.holdsLock(monitor); if (e.isPresent()) { finishedFuture.completeExceptionally(e.get()); } else { finishedFuture.complete(null); } }
private void writeBufferToOutputStream(ResponseContentPart contentPart) throws Throwable { callCompletionHandlerWhenDone(contentPart.handler, () -> { ByteBuffer buffer = contentPart.buf; final int bytesToSend = buffer.remaining(); try { if (buffer.hasArray()) { outputStream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining()); } else { final byte[] array = new byte[buffer.remaining()]; buffer.get(array); outputStream.write(array); } metricReporter.successfulWrite(bytesToSend); } catch (Throwable throwable) { metricReporter.failedWrite(); throw throwable; } }); }
private static void callCompletionHandlerWhenDone(CompletionHandler handler, IORunnable runnable) throws Exception { try { runnable.run(); } catch (Throwable e) { runCompletionHandler_logOnExceptions(() -> handler.failed(e)); throw e; } handler.completed(); //Might throw an exception, handling in the enclosing scope. }
public void fail(Throwable t) { setFinished(Optional.of(t)); }
@Override public void close(CompletionHandler handler) { commitResponse(); servletOutputStreamWriter.close(handlerOrNoopHandler(handler)); }
private void setResponse(Response jdiscResponse) { synchronized (monitor) { if (responseCommitted) { log.log(Level.FINE, jdiscResponse.getError(), () -> "Response already committed, can't change response code. " + "From: " + servletResponse.getStatus() + ", To: " + jdiscResponse.getStatus()); //TODO: should throw an exception here, but this breaks unit tests. //The failures will now instead happen when writing buffers. servletOutputStreamWriter.close(); return; } setStatus_holdingLock(jdiscResponse, servletResponse); setHeaders_holdingLock(jdiscResponse, servletResponse); } }