All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.fusionauth.http.server.HTTPResponseProcessor Maven / Gradle / Ivy

Go to download

An HTTP library for Java that provides a lightweight server (currently) and client (eventually) both with a goal of high-performance and simplicity

There is a newer version: 0.4.0-RC.3
Show newest version
/*
 * Copyright (c) 2022, FusionAuth, All Rights Reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */
package io.fusionauth.http.server;

import java.nio.ByteBuffer;

import io.fusionauth.http.Cookie;
import io.fusionauth.http.HTTPValues.Connections;
import io.fusionauth.http.HTTPValues.Headers;
import io.fusionauth.http.HTTPValues.TransferEncodings;
import io.fusionauth.http.body.response.BodyProcessor;
import io.fusionauth.http.body.response.ChunkedBodyProcessor;
import io.fusionauth.http.body.response.ContentLengthBodyProcessor;
import io.fusionauth.http.body.response.EmptyBodyProcessor;
import io.fusionauth.http.io.NonBlockingByteBufferOutputStream;
import io.fusionauth.http.log.Logger;
import io.fusionauth.http.util.HTTPTools;

/**
 * A processor that handles incoming bytes that form the HTTP request.
 *
 * @author Brian Pontarelli
 */
public class HTTPResponseProcessor {
  private final HTTPServerConfiguration configuration;

  private final Logger logger;

  private final NonBlockingByteBufferOutputStream outputStream;

  private final HTTPRequest request;

  private final HTTPResponse response;

  private BodyProcessor bodyProcessor;

  private ByteBuffer[] preambleBuffers;

  private volatile ResponseState state = ResponseState.Preamble;

  public HTTPResponseProcessor(HTTPServerConfiguration configuration, HTTPRequest request, HTTPResponse response,
                               NonBlockingByteBufferOutputStream outputStream) {
    this.configuration = configuration;
    this.request = request;
    this.response = response;
    this.outputStream = outputStream;
    this.logger = configuration.getLoggerFactory().getLogger(HTTPRequestProcessor.class);
  }

  public synchronized ByteBuffer[] currentBuffer() {
    if (state == ResponseState.Preamble || state == ResponseState.Expect) {
      // We can't write the preamble under normal conditions if the worker thread is still working. Expect handling is different and the
      // client is waiting for a pre-canned response
      if (state != ResponseState.Expect && outputStream.readableBuffer() == null && !outputStream.isClosed()) {
        return null;
      }

      // Construct the preamble if needed and return it if there is any bytes left
      if (preambleBuffers == null) {
        logger.debug("The worker thread has bytes to write or has closed the stream, but the preamble hasn't been sent yet. Generating preamble");
        int maxHeadLength = configuration.getMaxHeadLength();
        if (state == ResponseState.Preamble) {
          fillInHeaders();
          preambleBuffers = new ByteBuffer[]{HTTPTools.buildResponsePreamble(response, maxHeadLength)};
        } else if (state == ResponseState.Expect) {
          preambleBuffers = new ByteBuffer[]{HTTPTools.buildExpectResponsePreamble(response, maxHeadLength)};
        }

        logger.debug("Preamble is [{}] bytes long", preambleBuffers[0].remaining());
        if (logger.isDebuggable()) {
          logger.debug("Preamble is [\n{}\n]", new String(preambleBuffers[0].array(), 0, preambleBuffers[0].remaining()));
        }

        // Figure out the body processor
        Long contentLength = response.getContentLength();
        if (contentLength != null && contentLength > 0) {
          bodyProcessor = new ContentLengthBodyProcessor(outputStream);
        } else if (!outputStream.isEmpty()) {
          bodyProcessor = new ChunkedBodyProcessor(outputStream);

          var instrumenter = configuration.getInstrumenter();
          if (instrumenter != null) {
            instrumenter.chunkedResponse();
          }
        } else {
          bodyProcessor = new EmptyBodyProcessor();
        }
      }

      if (preambleBuffers[0].hasRemaining()) {
        logger.debug("Still writing preamble");
        return preambleBuffers;
      }

      // Reset the buffer in case we need to write another preamble (i.e. for expect)
      preambleBuffers = null;

      // If expect and preamble done, figure out stage to be Continue or Close
      if (state == ResponseState.Expect) {
        logger.debug("Expect response written");
        if (response.getStatus() == 100) {
          logger.debug("Continuing");
          state = ResponseState.Continue;
        } else {
          logger.debug("Closing");
          state = ResponseState.Close;
        }
      } else {
        // If not expect, next stage is Body
        state = ResponseState.Body;
      }
    } else if (state == ResponseState.Body) {
      ByteBuffer[] buffer = bodyProcessor.currentBuffers();
      if (buffer != null) {
        logger.debug("Writing back bytes");
        return buffer;
      }

      boolean complete = bodyProcessor.isComplete();
      if (complete) {
        // No more bytes and the stream is closed, figure out if we should Keep-Alive or Close
        state = response.isKeepAlive() ? ResponseState.KeepAlive : ResponseState.Close;
        logger.debug("No more bytes from worker thread. Changing state to [{}]", state);
      } else {
        // Just some debugging
        logger.debug("Nothing to write from the worker thread but the OutputStream isn't closed");
      }
    }

    return null;
  }

  public synchronized void failure() {
    // Go nuclear and wipe the response and stream, even if the response has already been committed (meaning one or more bytes have been
    // written)
    response.setStatus(500);
    response.setStatusMessage("Failure");
    response.clearHeaders();
    response.setContentLength(0L);

    preambleBuffers = null;

    outputStream.clear();
    outputStream.close();
    state = ResponseState.Preamble;
  }

  public void resetState(ResponseState state) {
    this.state = state;
  }

  public ResponseState state() {
    return state;
  }

  private void fillInHeaders() {
    // If the client wants the connection closed, force that in the response. This will force the code above to close the connection.
    // Otherwise, if the client asked for Keep-Alive and the server agrees, keep it. If the request asked for Keep-Alive, and the server
    // doesn't care, keep it. Otherwise, if the client and server both don't care, set to Keep-Alive.
    String requestConnection = request.getHeader(Headers.Connection);
    boolean requestKeepAlive = Connections.KeepAlive.equalsIgnoreCase(requestConnection);
    String responseConnection = response.getHeader(Headers.Connection);
    boolean responseKeepAlive = Connections.KeepAlive.equalsIgnoreCase(responseConnection);
    if (Connections.Close.equalsIgnoreCase(requestConnection)) {
      response.setHeader(Headers.Connection, Connections.Close);
    } else if ((requestKeepAlive && responseKeepAlive) || (requestKeepAlive && responseConnection == null) || (requestConnection == null && responseConnection == null)) {
      response.setHeader(Headers.Connection, Connections.KeepAlive);
    }

    Long contentLength = response.getContentLength();
    if (contentLength == null && outputStream.isEmpty()) {
      response.setContentLength(0L);
    } else if (contentLength == null) {
      response.setHeader(Headers.TransferEncoding, TransferEncodings.Chunked);
    }

    for (Cookie cookie : response.getCookies()) {
      String value = cookie.toResponseHeader();
      response.setHeader(Headers.SetCookie, value);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy