예제 #1
0
  /**
   * The local endpoint has reached a write failure.
   *
   * <p>A low level I/O failure, or even a jetty side EndPoint close (from idle timeout) are a few
   * scenarios
   *
   * @param t the throwable that caused the write failure
   */
  public void onWriteFailure(Throwable t) {
    ConnectionState event = null;
    synchronized (this) {
      if (this.state == ConnectionState.CLOSED) {
        // already closed
        return;
      }

      // Build out Close Reason
      String reason = "WebSocket Write Failure";
      if (t instanceof EOFException) {
        reason = "WebSocket Write EOF";
        Throwable cause = t.getCause();
        if ((cause != null) && (StringUtil.isNotBlank(cause.getMessage()))) {
          reason = "EOF: " + cause.getMessage();
        }
      } else {
        if (StringUtil.isNotBlank(t.getMessage())) {
          reason = t.getMessage();
        }
      }

      CloseInfo close = new CloseInfo(StatusCode.ABNORMAL, reason);

      finalClose.compareAndSet(null, close);

      this.cleanClose = false;
      this.state = ConnectionState.CLOSED;
      this.inputAvailable = false;
      this.outputAvailable = false;
      this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL;
      event = this.state;
    }
    notifyStateListeners(event);
  }
예제 #2
0
 /* ------------------------------------------------------------------------------- */
 public static void setJettyVersion(String serverVersion) {
   SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
   SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
   SEND[SEND_SERVER | SEND_XPOWEREDBY] =
       StringUtil.getBytes(
           "Server: " + serverVersion + "\015\012X-Powered-By: " + serverVersion + "\015\012");
 }
예제 #3
0
 /* ------------------------------------------------------------ */
 private void generateRequestLine(MetaData.Request request, ByteBuffer header) {
   header.put(StringUtil.getBytes(request.getMethod()));
   header.put((byte) ' ');
   header.put(StringUtil.getBytes(request.getURIString()));
   switch (request.getVersion()) {
     case HTTP_1_0:
     case HTTP_1_1:
       header.put((byte) ' ');
       header.put(request.getVersion().toBytes());
       break;
     default:
       throw new IllegalStateException();
   }
   header.put(HttpTokens.CRLF);
 }
예제 #4
0
  /**
   * Similar to the server side 6.2.3 testcase. Lots of small 1 byte UTF8 Text frames, representing
   * 1 overall text message.
   */
  @Test
  public void testParseCase6_2_3() {
    String utf8 = "Hello-\uC2B5@\uC39F\uC3A4\uC3BC\uC3A0\uC3A1-UTF-8!!";
    byte msg[] = StringUtil.getUtf8Bytes(utf8);

    List<WebSocketFrame> send = new ArrayList<>();
    int len = msg.length;
    byte opcode = OpCode.TEXT;
    byte mini[];
    for (int i = 0; i < len; i++) {
      WebSocketFrame frame = new WebSocketFrame(opcode);
      mini = new byte[1];
      mini[0] = msg[i];
      frame.setPayload(mini);
      boolean isLast = (i >= (len - 1));
      frame.setFin(isLast);
      send.add(frame);
      opcode = OpCode.CONTINUATION;
    }
    send.add(new CloseInfo(StatusCode.NORMAL).asFrame());

    ByteBuffer completeBuf = UnitGenerator.generate(send);
    UnitParser parser = new UnitParser();
    IncomingFramesCapture capture = new IncomingFramesCapture();
    parser.setIncomingFramesHandler(capture);
    parser.parse(completeBuf);

    capture.assertErrorCount(0);
    capture.assertHasFrame(OpCode.TEXT, len);
    capture.assertHasFrame(OpCode.CLOSE, 1);
  }
  @Override
  public Extension newInstance(ExtensionConfig config) {
    if (config == null) {
      return null;
    }

    String name = config.getName();
    if (StringUtil.isBlank(name)) {
      return null;
    }

    Class<? extends Extension> extClass = getExtension(name);
    if (extClass == null) {
      return null;
    }

    try {
      Extension ext = extClass.newInstance();
      if (ext instanceof AbstractExtension) {
        AbstractExtension aext = (AbstractExtension) ext;
        aext.setConfig(config);
        aext.setPolicy(policy);
        aext.setBufferPool(bufferPool);
      }
      return ext;
    } catch (InstantiationException | IllegalAccessException e) {
      throw new WebSocketException("Cannot instantiate extension: " + extClass, e);
    }
  }
예제 #6
0
  @Override
  public void setCharacterEncoding(String encoding) {
    if (isIncluding()) return;

    if (_outputType == OutputType.NONE && !isCommitted()) {
      if (encoding == null) {
        // Clear any encoding.
        if (_characterEncoding != null) {
          _characterEncoding = null;
          if (_contentType != null) {
            _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
            HttpField field = HttpField.CONTENT_TYPE.get(_contentType);
            if (field != null) _fields.put(field);
            else _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
          }
        }
      } else {
        // No, so just add this one to the mimetype
        _characterEncoding = StringUtil.normalizeCharset(encoding);
        if (_contentType != null) {
          _contentType =
              MimeTypes.getContentTypeWithoutCharset(_contentType)
                  + ";charset="
                  + _characterEncoding;
          HttpField field = HttpField.CONTENT_TYPE.get(_contentType);
          if (field != null) _fields.put(field);
          else _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
        }
      }
    }
  }
  public WebSocketSession(URI requestURI, EventDriver websocket, LogicalConnection connection) {
    if (requestURI == null) {
      throw new RuntimeException("Request URI cannot be null");
    }

    this.requestURI = requestURI;
    this.websocket = websocket;
    this.connection = connection;
    this.outgoingHandler = connection;
    this.incomingHandler = websocket;

    // Get the parameter map (use the jetty MultiMap to do this right)
    MultiMap<String> params = new MultiMap<>();
    String query = requestURI.getQuery();
    if (StringUtil.isNotBlank(query)) {
      UrlEncoded.decodeTo(query, params, StringUtil.__UTF8);
    }

    for (String name : params.keySet()) {
      List<String> valueList = params.getValues(name);
      String valueArr[] = new String[valueList.size()];
      valueArr = valueList.toArray(valueArr);
      parameterMap.put(name, valueArr);
    }
  }
예제 #8
0
  public ByteBuffer asByteBuffer() {
    if ((statusCode == StatusCode.NO_CLOSE)
        || (statusCode == StatusCode.NO_CODE)
        || (statusCode == (-1))) {
      // codes that are not allowed to be used in endpoint.
      return null;
    }

    ByteBuffer buf = ByteBuffer.allocate(WebSocketFrame.MAX_CONTROL_PAYLOAD);
    buf.putChar((char) statusCode);
    if (StringUtil.isNotBlank(reason)) {
      byte utf[] = StringUtil.getUtf8Bytes(reason);
      buf.put(utf, 0, utf.length);
    }
    BufferUtil.flipToFlush(buf, 0);
    return buf;
  }
예제 #9
0
  /* ------------------------------------------------------------ */
  private byte[] getReasonBytes(String reason) {
    if (reason.length() > 1024) reason = reason.substring(0, 1024);
    byte[] _bytes = StringUtil.getBytes(reason);

    for (int i = _bytes.length; i-- > 0; )
      if (_bytes[i] == '\r' || _bytes[i] == '\n') _bytes[i] = '?';
    return _bytes;
  }
예제 #10
0
 /**
  * Set a list of IP addresses that will not be rate limited.
  *
  * @param commaSeparatedList comma-separated whitelist
  */
 public void setWhitelist(String commaSeparatedList) {
   List<String> result = new ArrayList<>();
   for (String address : StringUtil.csvSplit(commaSeparatedList))
     addWhitelistAddress(result, address);
   clearWhitelist();
   _whitelist.addAll(result);
   LOG.debug("Whitelisted IP addresses: {}", result);
 }
예제 #11
0
    /** Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies */
    public void formatCookieDate(StringBuilder buf, long date) {
      gc.setTimeInMillis(date);

      int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
      int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
      int month = gc.get(Calendar.MONTH);
      int year = gc.get(Calendar.YEAR);
      year = year % 10000;

      int epoch = (int) ((date / 1000) % (60 * 60 * 24));
      int seconds = epoch % 60;
      epoch = epoch / 60;
      int minutes = epoch % 60;
      int hours = epoch / 60;

      buf.append(DAYS[day_of_week]);
      buf.append(',');
      buf.append(' ');
      StringUtil.append2digits(buf, day_of_month);

      buf.append('-');
      buf.append(MONTHS[month]);
      buf.append('-');
      StringUtil.append2digits(buf, year / 100);
      StringUtil.append2digits(buf, year % 100);

      buf.append(' ');
      StringUtil.append2digits(buf, hours);
      buf.append(':');
      StringUtil.append2digits(buf, minutes);
      buf.append(':');
      StringUtil.append2digits(buf, seconds);
      buf.append(" GMT");
    }
예제 #12
0
    /** Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" */
    public String formatDate(long date) {
      buf.setLength(0);
      gc.setTimeInMillis(date);

      int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
      int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
      int month = gc.get(Calendar.MONTH);
      int year = gc.get(Calendar.YEAR);
      int century = year / 100;
      year = year % 100;

      int hours = gc.get(Calendar.HOUR_OF_DAY);
      int minutes = gc.get(Calendar.MINUTE);
      int seconds = gc.get(Calendar.SECOND);

      buf.append(DAYS[day_of_week]);
      buf.append(',');
      buf.append(' ');
      StringUtil.append2digits(buf, day_of_month);

      buf.append(' ');
      buf.append(MONTHS[month]);
      buf.append(' ');
      StringUtil.append2digits(buf, century);
      StringUtil.append2digits(buf, year);

      buf.append(' ');
      StringUtil.append2digits(buf, hours);
      buf.append(':');
      StringUtil.append2digits(buf, minutes);
      buf.append(':');
      StringUtil.append2digits(buf, seconds);
      buf.append(" GMT");
      return buf.toString();
    }
  @Test
  public void testInflateBasics() throws Exception {
    // should result in "info:" text if properly inflated
    byte rawbuf[] = TypeUtil.fromHexString("CaCc4bCbB70200"); // what pywebsocket produces
    // byte rawbuf[] = TypeUtil.fromHexString("CbCc4bCbB70200"); // what java produces

    Inflater inflater = new Inflater(true);
    inflater.reset();
    inflater.setInput(rawbuf, 0, rawbuf.length);

    byte outbuf[] = new byte[64];
    int len = inflater.inflate(outbuf);
    inflater.end();
    Assert.assertThat("Inflated length", len, greaterThan(4));

    String actual = StringUtil.toUTF8String(outbuf, 0, len);
    Assert.assertThat("Inflated text", actual, is("info:"));
  }
  @Test
  public void testDeflateBasics() throws Exception {
    // Setup deflater basics
    boolean nowrap = true;
    Deflater compressor = new Deflater(Deflater.BEST_COMPRESSION, nowrap);
    compressor.setStrategy(Deflater.DEFAULT_STRATEGY);

    // Text to compress
    String text = "info:";
    byte uncompressed[] = StringUtil.getUtf8Bytes(text);

    // Prime the compressor
    compressor.reset();
    compressor.setInput(uncompressed, 0, uncompressed.length);
    compressor.finish();

    // Perform compression
    ByteBuffer outbuf = ByteBuffer.allocate(64);
    BufferUtil.clearToFill(outbuf);

    while (!compressor.finished()) {
      byte out[] = new byte[64];
      int len = compressor.deflate(out, 0, out.length, Deflater.SYNC_FLUSH);
      if (len > 0) {
        outbuf.put(out, 0, len);
      }
    }
    compressor.end();

    BufferUtil.flipToFlush(outbuf, 0);
    byte b0 = outbuf.get(0);
    if ((b0 & 1) != 0) {
      outbuf.put(0, (b0 ^= 1));
    }
    byte compressed[] = BufferUtil.toArray(outbuf);

    String actual = TypeUtil.toHexString(compressed);
    String expected = "CaCc4bCbB70200"; // what pywebsocket produces

    Assert.assertThat("Compressed data", actual, is(expected));
  }
예제 #15
0
  public void sendStandardRequest() throws IOException {
    StringBuilder req = new StringBuilder();
    req.append("GET /chat HTTP/1.1\r\n");
    req.append("Host: ").append(destHttpURI.getHost());
    if (destHttpURI.getPort() > 0) {
      req.append(':').append(destHttpURI.getPort());
    }
    req.append("\r\n");
    req.append("Upgrade: websocket\r\n");
    req.append("Connection: Upgrade\r\n");
    req.append("Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n");
    req.append("Sec-WebSocket-Origin: ").append(destWebsocketURI.toASCIIString()).append("\r\n");
    if (StringUtil.isNotBlank(protocols)) {
      req.append("Sec-WebSocket-Protocol: ").append(protocols).append("\r\n");
    }

    for (String xtension : extensions) {
      req.append("Sec-WebSocket-Extensions: ").append(xtension).append("\r\n");
    }
    req.append("Sec-WebSocket-Version: ").append(version).append("\r\n");
    req.append("\r\n");
    writeRaw(req.toString());
  }
예제 #16
0
/**
 * HttpGenerator. Builds HTTP Messages.
 *
 * <p>If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true, then the
 * generator will strictly pass on the exact strings received from methods and header fields.
 * Otherwise a fast case insensitive string lookup is used that may alter the case and white space
 * of some methods/headers
 */
public class HttpGenerator {
  private static final Logger LOG = Log.getLogger(HttpGenerator.class);

  public static final boolean __STRICT =
      Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT");

  private static final byte[] __colon_space = new byte[] {':', ' '};
  private static final HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
  public static final MetaData.Response CONTINUE_100_INFO =
      new MetaData.Response(HttpVersion.HTTP_1_1, 100, null, null, -1);
  public static final MetaData.Response PROGRESS_102_INFO =
      new MetaData.Response(HttpVersion.HTTP_1_1, 102, null, null, -1);
  public static final MetaData.Response RESPONSE_500_INFO =
      new MetaData.Response(
          HttpVersion.HTTP_1_1,
          HttpStatus.INTERNAL_SERVER_ERROR_500,
          null,
          new HttpFields() {
            {
              put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
            }
          },
          0);

  // states
  public enum State {
    START,
    COMMITTED,
    COMPLETING,
    COMPLETING_1XX,
    END
  }

  public enum Result {
    NEED_CHUNK,
    NEED_INFO,
    NEED_HEADER,
    FLUSH,
    CONTINUE,
    SHUTDOWN_OUT,
    DONE
  }

  // other statics
  public static final int CHUNK_SIZE = 12;

  private State _state = State.START;
  private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;

  private long _contentPrepared = 0;
  private boolean _noContent = false;
  private Boolean _persistent = null;

  private final int _send;
  private static final int SEND_SERVER = 0x01;
  private static final int SEND_XPOWEREDBY = 0x02;

  /* ------------------------------------------------------------------------------- */
  public static void setJettyVersion(String serverVersion) {
    SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
    SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
    SEND[SEND_SERVER | SEND_XPOWEREDBY] =
        StringUtil.getBytes(
            "Server: " + serverVersion + "\015\012X-Powered-By: " + serverVersion + "\015\012");
  }

  /* ------------------------------------------------------------------------------- */
  // data
  private boolean _needCRLF = false;

  /* ------------------------------------------------------------------------------- */
  public HttpGenerator() {
    this(false, false);
  }

  /* ------------------------------------------------------------------------------- */
  public HttpGenerator(boolean sendServerVersion, boolean sendXPoweredBy) {
    _send = (sendServerVersion ? SEND_SERVER : 0) | (sendXPoweredBy ? SEND_XPOWEREDBY : 0);
  }

  /* ------------------------------------------------------------------------------- */
  public void reset() {
    _state = State.START;
    _endOfContent = EndOfContent.UNKNOWN_CONTENT;
    _noContent = false;
    _persistent = null;
    _contentPrepared = 0;
    _needCRLF = false;
  }

  /* ------------------------------------------------------------ */
  @Deprecated
  public boolean getSendServerVersion() {
    return (_send & SEND_SERVER) != 0;
  }

  /* ------------------------------------------------------------ */
  @Deprecated
  public void setSendServerVersion(boolean sendServerVersion) {
    throw new UnsupportedOperationException();
  }

  /* ------------------------------------------------------------ */
  public State getState() {
    return _state;
  }

  /* ------------------------------------------------------------ */
  public boolean isState(State state) {
    return _state == state;
  }

  /* ------------------------------------------------------------ */
  public boolean isIdle() {
    return _state == State.START;
  }

  /* ------------------------------------------------------------ */
  public boolean isEnd() {
    return _state == State.END;
  }

  /* ------------------------------------------------------------ */
  public boolean isCommitted() {
    return _state.ordinal() >= State.COMMITTED.ordinal();
  }

  /* ------------------------------------------------------------ */
  public boolean isChunking() {
    return _endOfContent == EndOfContent.CHUNKED_CONTENT;
  }

  /* ------------------------------------------------------------ */
  public boolean isNoContent() {
    return _noContent;
  }

  /* ------------------------------------------------------------ */
  public void setPersistent(boolean persistent) {
    _persistent = persistent;
  }

  /* ------------------------------------------------------------ */
  /** @return true if known to be persistent */
  public boolean isPersistent() {
    return Boolean.TRUE.equals(_persistent);
  }

  /* ------------------------------------------------------------ */
  public boolean isWritten() {
    return _contentPrepared > 0;
  }

  /* ------------------------------------------------------------ */
  public long getContentPrepared() {
    return _contentPrepared;
  }

  /* ------------------------------------------------------------ */
  public void abort() {
    _persistent = false;
    _state = State.END;
    _endOfContent = null;
  }

  /* ------------------------------------------------------------ */
  public Result generateRequest(
      MetaData.Request info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last)
      throws IOException {
    switch (_state) {
      case START:
        {
          if (info == null) return Result.NEED_INFO;

          // Do we need a request header
          if (header == null) return Result.NEED_HEADER;

          // If we have not been told our persistence, set the default
          if (_persistent == null) {
            _persistent = info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal();
            if (!_persistent && HttpMethod.CONNECT.is(info.getMethod())) _persistent = true;
          }

          // prepare the header
          int pos = BufferUtil.flipToFill(header);
          try {
            // generate ResponseLine
            generateRequestLine(info, header);

            if (info.getVersion() == HttpVersion.HTTP_0_9) _noContent = true;
            else generateHeaders(info, header, content, last);

            boolean expect100 =
                info.getFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());

            if (expect100) {
              _state = State.COMMITTED;
            } else {
              // handle the content.
              int len = BufferUtil.length(content);
              if (len > 0) {
                _contentPrepared += len;
                if (isChunking()) prepareChunk(header, len);
              }
              _state = last ? State.COMPLETING : State.COMMITTED;
            }

            return Result.FLUSH;
          } catch (Exception e) {
            String message =
                (e instanceof BufferOverflowException)
                    ? "Request header too large"
                    : e.getMessage();
            throw new IOException(message, e);
          } finally {
            BufferUtil.flipToFlush(header, pos);
          }
        }

      case COMMITTED:
        {
          int len = BufferUtil.length(content);

          if (len > 0) {
            // Do we need a chunk buffer?
            if (isChunking()) {
              // Do we need a chunk buffer?
              if (chunk == null) return Result.NEED_CHUNK;
              BufferUtil.clearToFill(chunk);
              prepareChunk(chunk, len);
              BufferUtil.flipToFlush(chunk, 0);
            }
            _contentPrepared += len;
          }

          if (last) _state = State.COMPLETING;

          return len > 0 ? Result.FLUSH : Result.CONTINUE;
        }

      case COMPLETING:
        {
          if (BufferUtil.hasContent(content)) {
            if (LOG.isDebugEnabled()) LOG.debug("discarding content in COMPLETING");
            BufferUtil.clear(content);
          }

          if (isChunking()) {
            // Do we need a chunk buffer?
            if (chunk == null) return Result.NEED_CHUNK;
            BufferUtil.clearToFill(chunk);
            prepareChunk(chunk, 0);
            BufferUtil.flipToFlush(chunk, 0);
            _endOfContent = EndOfContent.UNKNOWN_CONTENT;
            return Result.FLUSH;
          }

          _state = State.END;
          return Boolean.TRUE.equals(_persistent) ? Result.DONE : Result.SHUTDOWN_OUT;
        }

      case END:
        if (BufferUtil.hasContent(content)) {
          if (LOG.isDebugEnabled()) LOG.debug("discarding content in COMPLETING");
          BufferUtil.clear(content);
        }
        return Result.DONE;

      default:
        throw new IllegalStateException();
    }
  }

  /* ------------------------------------------------------------ */
  public Result generateResponse(
      MetaData.Response info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last)
      throws IOException {
    return generateResponse(info, false, header, chunk, content, last);
  }

  /* ------------------------------------------------------------ */
  public Result generateResponse(
      MetaData.Response info,
      boolean head,
      ByteBuffer header,
      ByteBuffer chunk,
      ByteBuffer content,
      boolean last)
      throws IOException {
    switch (_state) {
      case START:
        {
          if (info == null) return Result.NEED_INFO;

          // Handle 0.9
          if (info.getVersion() == HttpVersion.HTTP_0_9) {
            _persistent = false;
            _endOfContent = EndOfContent.EOF_CONTENT;
            if (BufferUtil.hasContent(content)) _contentPrepared += content.remaining();
            _state = last ? State.COMPLETING : State.COMMITTED;
            return Result.FLUSH;
          }

          // Do we need a response header
          if (header == null) return Result.NEED_HEADER;

          // If we have not been told our persistence, set the default
          if (_persistent == null)
            _persistent = (info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());

          // prepare the header
          int pos = BufferUtil.flipToFill(header);
          try {
            // generate ResponseLine
            generateResponseLine(info, header);

            // Handle 1xx and no content responses
            int status = info.getStatus();
            if (status >= 100 && status < 200) {
              _noContent = true;

              if (status != HttpStatus.SWITCHING_PROTOCOLS_101) {
                header.put(HttpTokens.CRLF);
                _state = State.COMPLETING_1XX;
                return Result.FLUSH;
              }
            } else if (status == HttpStatus.NO_CONTENT_204
                || status == HttpStatus.NOT_MODIFIED_304) {
              _noContent = true;
            }

            generateHeaders(info, header, content, last);

            // handle the content.
            int len = BufferUtil.length(content);
            if (len > 0) {
              _contentPrepared += len;
              if (isChunking() && !head) prepareChunk(header, len);
            }
            _state = last ? State.COMPLETING : State.COMMITTED;
          } catch (Exception e) {
            String message =
                (e instanceof BufferOverflowException)
                    ? "Response header too large"
                    : e.getMessage();
            throw new IOException(message, e);
          } finally {
            BufferUtil.flipToFlush(header, pos);
          }

          return Result.FLUSH;
        }

      case COMMITTED:
        {
          int len = BufferUtil.length(content);

          // handle the content.
          if (len > 0) {
            if (isChunking()) {
              if (chunk == null) return Result.NEED_CHUNK;
              BufferUtil.clearToFill(chunk);
              prepareChunk(chunk, len);
              BufferUtil.flipToFlush(chunk, 0);
            }
            _contentPrepared += len;
          }

          if (last) {
            _state = State.COMPLETING;
            return len > 0 ? Result.FLUSH : Result.CONTINUE;
          }
          return len > 0 ? Result.FLUSH : Result.DONE;
        }

      case COMPLETING_1XX:
        {
          reset();
          return Result.DONE;
        }

      case COMPLETING:
        {
          if (BufferUtil.hasContent(content)) {
            if (LOG.isDebugEnabled()) LOG.debug("discarding content in COMPLETING");
            BufferUtil.clear(content);
          }

          if (isChunking()) {
            // Do we need a chunk buffer?
            if (chunk == null) return Result.NEED_CHUNK;

            // Write the last chunk
            BufferUtil.clearToFill(chunk);
            prepareChunk(chunk, 0);
            BufferUtil.flipToFlush(chunk, 0);
            _endOfContent = EndOfContent.UNKNOWN_CONTENT;
            return Result.FLUSH;
          }

          _state = State.END;

          return Boolean.TRUE.equals(_persistent) ? Result.DONE : Result.SHUTDOWN_OUT;
        }

      case END:
        if (BufferUtil.hasContent(content)) {
          if (LOG.isDebugEnabled()) LOG.debug("discarding content in COMPLETING");
          BufferUtil.clear(content);
        }
        return Result.DONE;

      default:
        throw new IllegalStateException();
    }
  }

  /* ------------------------------------------------------------ */
  private void prepareChunk(ByteBuffer chunk, int remaining) {
    // if we need CRLF add this to header
    if (_needCRLF) BufferUtil.putCRLF(chunk);

    // Add the chunk size to the header
    if (remaining > 0) {
      BufferUtil.putHexInt(chunk, remaining);
      BufferUtil.putCRLF(chunk);
      _needCRLF = true;
    } else {
      chunk.put(LAST_CHUNK);
      _needCRLF = false;
    }
  }

  /* ------------------------------------------------------------ */
  private void generateRequestLine(MetaData.Request request, ByteBuffer header) {
    header.put(StringUtil.getBytes(request.getMethod()));
    header.put((byte) ' ');
    header.put(StringUtil.getBytes(request.getURIString()));
    switch (request.getVersion()) {
      case HTTP_1_0:
      case HTTP_1_1:
        header.put((byte) ' ');
        header.put(request.getVersion().toBytes());
        break;
      default:
        throw new IllegalStateException();
    }
    header.put(HttpTokens.CRLF);
  }

  /* ------------------------------------------------------------ */
  private void generateResponseLine(MetaData.Response response, ByteBuffer header) {
    // Look for prepared response line
    int status = response.getStatus();
    PreparedResponse preprepared = status < __preprepared.length ? __preprepared[status] : null;
    String reason = response.getReason();
    if (preprepared != null) {
      if (reason == null) header.put(preprepared._responseLine);
      else {
        header.put(preprepared._schemeCode);
        header.put(getReasonBytes(reason));
        header.put(HttpTokens.CRLF);
      }
    } else // generate response line
    {
      header.put(HTTP_1_1_SPACE);
      header.put((byte) ('0' + status / 100));
      header.put((byte) ('0' + (status % 100) / 10));
      header.put((byte) ('0' + (status % 10)));
      header.put((byte) ' ');
      if (reason == null) {
        header.put((byte) ('0' + status / 100));
        header.put((byte) ('0' + (status % 100) / 10));
        header.put((byte) ('0' + (status % 10)));
      } else header.put(getReasonBytes(reason));
      header.put(HttpTokens.CRLF);
    }
  }

  /* ------------------------------------------------------------ */
  private byte[] getReasonBytes(String reason) {
    if (reason.length() > 1024) reason = reason.substring(0, 1024);
    byte[] _bytes = StringUtil.getBytes(reason);

    for (int i = _bytes.length; i-- > 0; )
      if (_bytes[i] == '\r' || _bytes[i] == '\n') _bytes[i] = '?';
    return _bytes;
  }

  /* ------------------------------------------------------------ */
  private void generateHeaders(
      MetaData _info, ByteBuffer header, ByteBuffer content, boolean last) {
    final MetaData.Request request =
        (_info instanceof MetaData.Request) ? (MetaData.Request) _info : null;
    final MetaData.Response response =
        (_info instanceof MetaData.Response) ? (MetaData.Response) _info : null;

    // default field values
    int send = _send;
    HttpField transfer_encoding = null;
    boolean keep_alive = false;
    boolean close = false;
    boolean content_type = false;
    StringBuilder connection = null;

    // Generate fields
    if (_info.getFields() != null) {
      for (HttpField field : _info.getFields()) {
        String v = field.getValue();
        if (v == null || v.length() == 0) continue; // rfc7230 does not allow no value

        HttpHeader h = field.getHeader();

        switch (h == null ? HttpHeader.UNKNOWN : h) {
          case CONTENT_LENGTH:
            // handle specially below
            if (_info.getContentLength() >= 0) _endOfContent = EndOfContent.CONTENT_LENGTH;
            break;

          case CONTENT_TYPE:
            {
              if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString()))
                _endOfContent = EndOfContent.SELF_DEFINING_CONTENT;

              // write the field to the header
              content_type = true;
              putTo(field, header);
              break;
            }

          case TRANSFER_ENCODING:
            {
              if (_info.getVersion() == HttpVersion.HTTP_1_1) transfer_encoding = field;
              // Do NOT add yet!
              break;
            }

          case CONNECTION:
            {
              if (request != null) putTo(field, header);

              // Lookup and/or split connection value field
              HttpHeaderValue[] values =
                  HttpHeaderValue.CLOSE.is(field.getValue())
                      ? CLOSE
                      : new HttpHeaderValue[] {HttpHeaderValue.CACHE.get(field.getValue())};
              String[] split = null;

              if (values[0] == null) {
                split = StringUtil.csvSplit(field.getValue());
                if (split.length > 0) {
                  values = new HttpHeaderValue[split.length];
                  for (int i = 0; i < split.length; i++)
                    values[i] = HttpHeaderValue.CACHE.get(split[i]);
                }
              }

              // Handle connection values
              for (int i = 0; i < values.length; i++) {
                HttpHeaderValue value = values[i];
                switch (value == null ? HttpHeaderValue.UNKNOWN : value) {
                  case UPGRADE:
                    {
                      // special case for websocket connection ordering
                      header
                          .put(HttpHeader.CONNECTION.getBytesColonSpace())
                          .put(HttpHeader.UPGRADE.getBytes());
                      header.put(CRLF);
                      break;
                    }

                  case CLOSE:
                    {
                      close = true;
                      _persistent = false;
                      if (response != null) {
                        if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
                          _endOfContent = EndOfContent.EOF_CONTENT;
                      }
                      break;
                    }

                  case KEEP_ALIVE:
                    {
                      if (_info.getVersion() == HttpVersion.HTTP_1_0) {
                        keep_alive = true;
                        if (response != null) _persistent = true;
                      }
                      break;
                    }

                  default:
                    {
                      if (connection == null) connection = new StringBuilder();
                      else connection.append(',');
                      connection.append(split == null ? field.getValue() : split[i]);
                    }
                }
              }

              // Do NOT add yet!
              break;
            }

          case SERVER:
            {
              send = send & ~SEND_SERVER;
              putTo(field, header);
              break;
            }

          default:
            putTo(field, header);
        }
      }
    }

    // Calculate how to end _content and connection, _content length and transfer encoding
    // settings.
    // From RFC 2616 4.4:
    // 1. No body for 1xx, 204, 304 & HEAD response
    // 2. Force _content-length?
    // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
    // 4. Content-Length
    // 5. multipart/byteranges
    // 6. close
    int status = response != null ? response.getStatus() : -1;
    switch (_endOfContent) {
      case UNKNOWN_CONTENT:
        // It may be that we have no _content, or perhaps _content just has not been
        // written yet?

        // Response known not to have a body
        if (_contentPrepared == 0
            && response != null
            && (status < 200 || status == 204 || status == 304))
          _endOfContent = EndOfContent.NO_CONTENT;
        else if (_info.getContentLength() > 0) {
          // we have been given a content length
          _endOfContent = EndOfContent.CONTENT_LENGTH;
          long content_length = _info.getContentLength();
          if ((response != null || content_length > 0 || content_type) && !_noContent) {
            // known length but not actually set.
            header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
            BufferUtil.putDecLong(header, content_length);
            header.put(HttpTokens.CRLF);
          }
        } else if (last) {
          // we have seen all the _content there is, so we can be content-length limited.
          _endOfContent = EndOfContent.CONTENT_LENGTH;
          long content_length = _contentPrepared + BufferUtil.length(content);

          // Do we need to tell the headers about it
          if ((response != null || content_length > 0 || content_type) && !_noContent) {
            header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
            BufferUtil.putDecLong(header, content_length);
            header.put(HttpTokens.CRLF);
          }
        } else {
          // No idea, so we must assume that a body is coming.
          _endOfContent = EndOfContent.CHUNKED_CONTENT;
          // HTTP 1.0 does not understand chunked content, so we must use EOF content.
          // For a request with HTTP 1.0 & Connection: keep-alive
          // we *must* close the connection, otherwise the client
          // has no way to detect the end of the content.
          if (!isPersistent() || _info.getVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
            _endOfContent = EndOfContent.EOF_CONTENT;
        }
        break;

      case CONTENT_LENGTH:
        {
          long content_length = _info.getContentLength();
          if ((response != null || content_length > 0 || content_type) && !_noContent) {
            header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
            BufferUtil.putDecLong(header, content_length);
            header.put(HttpTokens.CRLF);
          }
          break;
        }

      case SELF_DEFINING_CONTENT:
        {
          // TODO - Should we do this? Why was it not required before?
          long content_length = _info.getContentLength();
          if (content_length > 0) {
            header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
            BufferUtil.putDecLong(header, content_length);
            header.put(HttpTokens.CRLF);
          }
          break;
        }
      case NO_CONTENT:
        if (response != null && status >= 200 && status != 204 && status != 304)
          header.put(CONTENT_LENGTH_0);
        break;

      case EOF_CONTENT:
        _persistent = request != null;
        break;

      case CHUNKED_CONTENT:
        break;

      default:
        break;
    }

    // Add transfer_encoding if needed
    if (isChunking()) {
      // try to use user supplied encoding as it may have other values.
      if (transfer_encoding != null
          && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue())) {
        String c = transfer_encoding.getValue();
        if (c.endsWith(HttpHeaderValue.CHUNKED.toString())) putTo(transfer_encoding, header);
        else throw new IllegalArgumentException("BAD TE");
      } else header.put(TRANSFER_ENCODING_CHUNKED);
    }

    // Handle connection if need be
    if (_endOfContent == EndOfContent.EOF_CONTENT) {
      keep_alive = false;
      _persistent = false;
    }

    // If this is a response, work out persistence
    if (response != null) {
      if (!isPersistent()
          && (close || _info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal())) {
        if (connection == null) header.put(CONNECTION_CLOSE);
        else {
          header.put(CONNECTION_CLOSE, 0, CONNECTION_CLOSE.length - 2);
          header.put((byte) ',');
          header.put(StringUtil.getBytes(connection.toString()));
          header.put(CRLF);
        }
      } else if (keep_alive) {
        if (connection == null) header.put(CONNECTION_KEEP_ALIVE);
        else {
          header.put(CONNECTION_KEEP_ALIVE, 0, CONNECTION_KEEP_ALIVE.length - 2);
          header.put((byte) ',');
          header.put(StringUtil.getBytes(connection.toString()));
          header.put(CRLF);
        }
      } else if (connection != null) {
        header.put(HttpHeader.CONNECTION.getBytesColonSpace());
        header.put(StringUtil.getBytes(connection.toString()));
        header.put(CRLF);
      }
    }

    if (status > 199) header.put(SEND[send]);

    // end the header.
    header.put(HttpTokens.CRLF);
  }

  /* ------------------------------------------------------------------------------- */
  public static byte[] getReasonBuffer(int code) {
    PreparedResponse status = code < __preprepared.length ? __preprepared[code] : null;
    if (status != null) return status._reason;
    return null;
  }

  /* ------------------------------------------------------------------------------- */
  @Override
  public String toString() {
    return String.format("%s@%x{s=%s}", getClass().getSimpleName(), hashCode(), _state);
  }

  /* ------------------------------------------------------------------------------- */
  /* ------------------------------------------------------------------------------- */
  /* ------------------------------------------------------------------------------- */
  // common _content
  private static final byte[] LAST_CHUNK = {
    (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'
  };
  private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
  private static final byte[] CONNECTION_KEEP_ALIVE =
      StringUtil.getBytes("Connection: keep-alive\015\012");
  private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
  private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1 + " ");
  private static final byte[] CRLF = StringUtil.getBytes("\015\012");
  private static final byte[] TRANSFER_ENCODING_CHUNKED =
      StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
  private static final byte[][] SEND =
      new byte[][] {
        new byte[0],
        StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"),
        StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"),
        StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012")
      };

  /* ------------------------------------------------------------------------------- */
  /* ------------------------------------------------------------------------------- */
  /* ------------------------------------------------------------------------------- */
  // Build cache of response lines for status
  private static class PreparedResponse {
    byte[] _reason;
    byte[] _schemeCode;
    byte[] _responseLine;
  }

  private static final PreparedResponse[] __preprepared =
      new PreparedResponse[HttpStatus.MAX_CODE + 1];

  static {
    int versionLength = HttpVersion.HTTP_1_1.toString().length();

    for (int i = 0; i < __preprepared.length; i++) {
      HttpStatus.Code code = HttpStatus.getCode(i);
      if (code == null) continue;
      String reason = code.getMessage();
      byte[] line = new byte[versionLength + 5 + reason.length() + 2];
      HttpVersion.HTTP_1_1.toBuffer().get(line, 0, versionLength);
      line[versionLength + 0] = ' ';
      line[versionLength + 1] = (byte) ('0' + i / 100);
      line[versionLength + 2] = (byte) ('0' + (i % 100) / 10);
      line[versionLength + 3] = (byte) ('0' + (i % 10));
      line[versionLength + 4] = ' ';
      for (int j = 0; j < reason.length(); j++)
        line[versionLength + 5 + j] = (byte) reason.charAt(j);
      line[versionLength + 5 + reason.length()] = HttpTokens.CARRIAGE_RETURN;
      line[versionLength + 6 + reason.length()] = HttpTokens.LINE_FEED;

      __preprepared[i] = new PreparedResponse();
      __preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0, versionLength + 5);
      __preprepared[i]._reason = Arrays.copyOfRange(line, versionLength + 5, line.length - 2);
      __preprepared[i]._responseLine = line;
    }
  }

  private static void putSanitisedName(String s, ByteBuffer buffer) {
    int l = s.length();
    for (int i = 0; i < l; i++) {
      char c = s.charAt(i);

      if (c < 0 || c > 0xff || c == '\r' || c == '\n' || c == ':') buffer.put((byte) '?');
      else buffer.put((byte) (0xff & c));
    }
  }

  private static void putSanitisedValue(String s, ByteBuffer buffer) {
    int l = s.length();
    for (int i = 0; i < l; i++) {
      char c = s.charAt(i);

      if (c < 0 || c > 0xff || c == '\r' || c == '\n') buffer.put((byte) ' ');
      else buffer.put((byte) (0xff & c));
    }
  }

  public static void putTo(HttpField field, ByteBuffer bufferInFillMode) {
    if (field instanceof PreEncodedHttpField) {
      ((PreEncodedHttpField) field).putTo(bufferInFillMode, HttpVersion.HTTP_1_0);
    } else {
      HttpHeader header = field.getHeader();
      if (header != null) {
        bufferInFillMode.put(header.getBytesColonSpace());
        putSanitisedValue(field.getValue(), bufferInFillMode);
      } else {
        putSanitisedName(field.getName(), bufferInFillMode);
        bufferInFillMode.put(__colon_space);
        putSanitisedValue(field.getValue(), bufferInFillMode);
      }

      BufferUtil.putCRLF(bufferInFillMode);
    }
  }

  public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode) {
    for (HttpField field : fields) {
      if (field != null) putTo(field, bufferInFillMode);
    }
    BufferUtil.putCRLF(bufferInFillMode);
  }
}
예제 #17
0
 private static String deTag(String raw) {
   return StringUtil.replace(StringUtil.replace(raw, "<", "&lt;"), ">", "&gt;");
 }
예제 #18
0
  private boolean handleKnownHeaders(ByteBuffer buffer) {
    boolean add_to_connection_trie = false;
    switch (_header) {
      case CONTENT_LENGTH:
        if (_endOfContent != EndOfContent.CHUNKED_CONTENT) {
          try {
            _contentLength = Long.parseLong(_valueString);
          } catch (NumberFormatException e) {
            LOG.ignore(e);
            throw new BadMessage(HttpStatus.BAD_REQUEST_400, "Bad Content-Length");
          }
          if (_contentLength <= 0) _endOfContent = EndOfContent.NO_CONTENT;
          else _endOfContent = EndOfContent.CONTENT_LENGTH;
        }
        break;

      case TRANSFER_ENCODING:
        if (_value == HttpHeaderValue.CHUNKED) _endOfContent = EndOfContent.CHUNKED_CONTENT;
        else {
          if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
            _endOfContent = EndOfContent.CHUNKED_CONTENT;
          else if (_valueString.indexOf(HttpHeaderValue.CHUNKED.toString()) >= 0) {
            throw new BadMessage(HttpStatus.BAD_REQUEST_400, "Bad chunking");
          }
        }
        break;

      case HOST:
        add_to_connection_trie = _connectionFields != null && _field == null;
        _host = true;
        String host = _valueString;
        int port = 0;
        if (host == null || host.length() == 0) {
          throw new BadMessage(HttpStatus.BAD_REQUEST_400, "Bad Host header");
        }

        int len = host.length();
        loop:
        for (int i = len; i-- > 0; ) {
          char c2 = (char) (0xff & host.charAt(i));
          switch (c2) {
            case ']':
              break loop;

            case ':':
              try {
                len = i;
                port = StringUtil.toInt(host.substring(i + 1));
              } catch (NumberFormatException e) {
                if (DEBUG) LOG.debug(e);
                throw new BadMessage(HttpStatus.BAD_REQUEST_400, "Bad Host header");
              }
              break loop;
          }
        }
        if (host.charAt(0) == '[') {
          if (host.charAt(len - 1) != ']') {
            throw new BadMessage(HttpStatus.BAD_REQUEST_400, "Bad IPv6 Host header");
          }
          host = host.substring(1, len - 1);
        } else if (len != host.length()) host = host.substring(0, len);

        if (_requestHandler != null) _requestHandler.parsedHostHeader(host, port);

        break;

      case CONNECTION:
        // Don't cache if not persistent
        if (_valueString != null && _valueString.indexOf("close") >= 0) {
          _closed = true;
          _connectionFields = null;
        }
        break;

      case AUTHORIZATION:
      case ACCEPT:
      case ACCEPT_CHARSET:
      case ACCEPT_ENCODING:
      case ACCEPT_LANGUAGE:
      case COOKIE:
      case CACHE_CONTROL:
      case USER_AGENT:
        add_to_connection_trie = _connectionFields != null && _field == null;
        break;

      default:
        break;
    }

    if (add_to_connection_trie
        && !_connectionFields.isFull()
        && _header != null
        && _valueString != null) {
      _field = new HttpField(_header, _valueString);
      _connectionFields.put(_field);
    }

    return false;
  }
  protected void commit(ByteBuffer content, boolean complete, Callback callback) {
    // Are we excluding because of status?
    Response response = _channel.getResponse();
    int sc = response.getStatus();
    if (sc > 0 && (sc < 200 || sc == 204 || sc == 205 || sc >= 300)) {
      LOG.debug("{} exclude by status {}", this, sc);
      noCompression();

      if (sc == 304) {
        String request_etags =
            (String) _channel.getRequest().getAttribute("o.e.j.s.h.gzip.GzipHandler.etag");
        String response_etag = response.getHttpFields().get(HttpHeader.ETAG);
        if (request_etags != null && response_etag != null) {
          String response_etag_gzip = etagGzip(response_etag);
          if (request_etags.contains(response_etag_gzip))
            response.getHttpFields().put(HttpHeader.ETAG, response_etag_gzip);
        }
      }

      _interceptor.write(content, complete, callback);
      return;
    }

    // Are we excluding because of mime-type?
    String ct = response.getContentType();
    if (ct != null) {
      ct = MimeTypes.getContentTypeWithoutCharset(ct);
      if (!_factory.isMimeTypeGzipable(StringUtil.asciiToLowerCase(ct))) {
        LOG.debug("{} exclude by mimeType {}", this, ct);
        noCompression();
        _interceptor.write(content, complete, callback);
        return;
      }
    }

    // Has the Content-Encoding header already been set?
    HttpFields fields = response.getHttpFields();
    String ce = fields.get(HttpHeader.CONTENT_ENCODING);
    if (ce != null) {
      LOG.debug("{} exclude by content-encoding {}", this, ce);
      noCompression();
      _interceptor.write(content, complete, callback);
      return;
    }

    // Are we the thread that commits?
    if (_state.compareAndSet(GZState.MIGHT_COMPRESS, GZState.COMMITTING)) {
      // We are varying the response due to accept encoding header.
      if (_vary != null) {
        if (fields.contains(HttpHeader.VARY)) fields.addCSV(HttpHeader.VARY, _vary.getValues());
        else fields.add(_vary);
      }

      long content_length = response.getContentLength();
      if (content_length < 0 && complete) content_length = content.remaining();

      _deflater = _factory.getDeflater(_channel.getRequest(), content_length);

      if (_deflater == null) {
        LOG.debug("{} exclude no deflater", this);
        _state.set(GZState.NOT_COMPRESSING);
        _interceptor.write(content, complete, callback);
        return;
      }

      fields.put(GZIP._contentEncoding);
      _crc.reset();
      _buffer = _channel.getByteBufferPool().acquire(_bufferSize, false);
      BufferUtil.fill(_buffer, GZIP_HEADER, 0, GZIP_HEADER.length);

      // Adjust headers
      response.setContentLength(-1);
      String etag = fields.get(HttpHeader.ETAG);
      if (etag != null) fields.put(HttpHeader.ETAG, etagGzip(etag));

      LOG.debug("{} compressing {}", this, _deflater);
      _state.set(GZState.COMPRESSING);

      gzip(content, complete, callback);
    } else callback.failed(new WritePendingException());
  }
예제 #20
0
  /* ------------------------------------------------------------ */
  private void generateHeaders(
      MetaData _info, ByteBuffer header, ByteBuffer content, boolean last) {
    final MetaData.Request request =
        (_info instanceof MetaData.Request) ? (MetaData.Request) _info : null;
    final MetaData.Response response =
        (_info instanceof MetaData.Response) ? (MetaData.Response) _info : null;

    // default field values
    int send = _send;
    HttpField transfer_encoding = null;
    boolean keep_alive = false;
    boolean close = false;
    boolean content_type = false;
    StringBuilder connection = null;

    // Generate fields
    if (_info.getFields() != null) {
      for (HttpField field : _info.getFields()) {
        String v = field.getValue();
        if (v == null || v.length() == 0) continue; // rfc7230 does not allow no value

        HttpHeader h = field.getHeader();

        switch (h == null ? HttpHeader.UNKNOWN : h) {
          case CONTENT_LENGTH:
            // handle specially below
            if (_info.getContentLength() >= 0) _endOfContent = EndOfContent.CONTENT_LENGTH;
            break;

          case CONTENT_TYPE:
            {
              if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString()))
                _endOfContent = EndOfContent.SELF_DEFINING_CONTENT;

              // write the field to the header
              content_type = true;
              putTo(field, header);
              break;
            }

          case TRANSFER_ENCODING:
            {
              if (_info.getVersion() == HttpVersion.HTTP_1_1) transfer_encoding = field;
              // Do NOT add yet!
              break;
            }

          case CONNECTION:
            {
              if (request != null) putTo(field, header);

              // Lookup and/or split connection value field
              HttpHeaderValue[] values =
                  HttpHeaderValue.CLOSE.is(field.getValue())
                      ? CLOSE
                      : new HttpHeaderValue[] {HttpHeaderValue.CACHE.get(field.getValue())};
              String[] split = null;

              if (values[0] == null) {
                split = StringUtil.csvSplit(field.getValue());
                if (split.length > 0) {
                  values = new HttpHeaderValue[split.length];
                  for (int i = 0; i < split.length; i++)
                    values[i] = HttpHeaderValue.CACHE.get(split[i]);
                }
              }

              // Handle connection values
              for (int i = 0; i < values.length; i++) {
                HttpHeaderValue value = values[i];
                switch (value == null ? HttpHeaderValue.UNKNOWN : value) {
                  case UPGRADE:
                    {
                      // special case for websocket connection ordering
                      header
                          .put(HttpHeader.CONNECTION.getBytesColonSpace())
                          .put(HttpHeader.UPGRADE.getBytes());
                      header.put(CRLF);
                      break;
                    }

                  case CLOSE:
                    {
                      close = true;
                      _persistent = false;
                      if (response != null) {
                        if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
                          _endOfContent = EndOfContent.EOF_CONTENT;
                      }
                      break;
                    }

                  case KEEP_ALIVE:
                    {
                      if (_info.getVersion() == HttpVersion.HTTP_1_0) {
                        keep_alive = true;
                        if (response != null) _persistent = true;
                      }
                      break;
                    }

                  default:
                    {
                      if (connection == null) connection = new StringBuilder();
                      else connection.append(',');
                      connection.append(split == null ? field.getValue() : split[i]);
                    }
                }
              }

              // Do NOT add yet!
              break;
            }

          case SERVER:
            {
              send = send & ~SEND_SERVER;
              putTo(field, header);
              break;
            }

          default:
            putTo(field, header);
        }
      }
    }

    // Calculate how to end _content and connection, _content length and transfer encoding
    // settings.
    // From RFC 2616 4.4:
    // 1. No body for 1xx, 204, 304 & HEAD response
    // 2. Force _content-length?
    // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
    // 4. Content-Length
    // 5. multipart/byteranges
    // 6. close
    int status = response != null ? response.getStatus() : -1;
    switch (_endOfContent) {
      case UNKNOWN_CONTENT:
        // It may be that we have no _content, or perhaps _content just has not been
        // written yet?

        // Response known not to have a body
        if (_contentPrepared == 0
            && response != null
            && (status < 200 || status == 204 || status == 304))
          _endOfContent = EndOfContent.NO_CONTENT;
        else if (_info.getContentLength() > 0) {
          // we have been given a content length
          _endOfContent = EndOfContent.CONTENT_LENGTH;
          long content_length = _info.getContentLength();
          if ((response != null || content_length > 0 || content_type) && !_noContent) {
            // known length but not actually set.
            header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
            BufferUtil.putDecLong(header, content_length);
            header.put(HttpTokens.CRLF);
          }
        } else if (last) {
          // we have seen all the _content there is, so we can be content-length limited.
          _endOfContent = EndOfContent.CONTENT_LENGTH;
          long content_length = _contentPrepared + BufferUtil.length(content);

          // Do we need to tell the headers about it
          if ((response != null || content_length > 0 || content_type) && !_noContent) {
            header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
            BufferUtil.putDecLong(header, content_length);
            header.put(HttpTokens.CRLF);
          }
        } else {
          // No idea, so we must assume that a body is coming.
          _endOfContent = EndOfContent.CHUNKED_CONTENT;
          // HTTP 1.0 does not understand chunked content, so we must use EOF content.
          // For a request with HTTP 1.0 & Connection: keep-alive
          // we *must* close the connection, otherwise the client
          // has no way to detect the end of the content.
          if (!isPersistent() || _info.getVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
            _endOfContent = EndOfContent.EOF_CONTENT;
        }
        break;

      case CONTENT_LENGTH:
        {
          long content_length = _info.getContentLength();
          if ((response != null || content_length > 0 || content_type) && !_noContent) {
            header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
            BufferUtil.putDecLong(header, content_length);
            header.put(HttpTokens.CRLF);
          }
          break;
        }

      case SELF_DEFINING_CONTENT:
        {
          // TODO - Should we do this? Why was it not required before?
          long content_length = _info.getContentLength();
          if (content_length > 0) {
            header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
            BufferUtil.putDecLong(header, content_length);
            header.put(HttpTokens.CRLF);
          }
          break;
        }
      case NO_CONTENT:
        if (response != null && status >= 200 && status != 204 && status != 304)
          header.put(CONTENT_LENGTH_0);
        break;

      case EOF_CONTENT:
        _persistent = request != null;
        break;

      case CHUNKED_CONTENT:
        break;

      default:
        break;
    }

    // Add transfer_encoding if needed
    if (isChunking()) {
      // try to use user supplied encoding as it may have other values.
      if (transfer_encoding != null
          && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue())) {
        String c = transfer_encoding.getValue();
        if (c.endsWith(HttpHeaderValue.CHUNKED.toString())) putTo(transfer_encoding, header);
        else throw new IllegalArgumentException("BAD TE");
      } else header.put(TRANSFER_ENCODING_CHUNKED);
    }

    // Handle connection if need be
    if (_endOfContent == EndOfContent.EOF_CONTENT) {
      keep_alive = false;
      _persistent = false;
    }

    // If this is a response, work out persistence
    if (response != null) {
      if (!isPersistent()
          && (close || _info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal())) {
        if (connection == null) header.put(CONNECTION_CLOSE);
        else {
          header.put(CONNECTION_CLOSE, 0, CONNECTION_CLOSE.length - 2);
          header.put((byte) ',');
          header.put(StringUtil.getBytes(connection.toString()));
          header.put(CRLF);
        }
      } else if (keep_alive) {
        if (connection == null) header.put(CONNECTION_KEEP_ALIVE);
        else {
          header.put(CONNECTION_KEEP_ALIVE, 0, CONNECTION_KEEP_ALIVE.length - 2);
          header.put((byte) ',');
          header.put(StringUtil.getBytes(connection.toString()));
          header.put(CRLF);
        }
      } else if (connection != null) {
        header.put(HttpHeader.CONNECTION.getBytesColonSpace());
        header.put(StringUtil.getBytes(connection.toString()));
        header.put(CRLF);
      }
    }

    if (status > 199) header.put(SEND[send]);

    // end the header.
    header.put(HttpTokens.CRLF);
  }
예제 #21
0
  @Override
  public void sendError(int code, String message) throws IOException {
    if (isIncluding()) return;

    if (isCommitted()) LOG.warn("Committed before " + code + " " + message);

    resetBuffer();
    _characterEncoding = null;
    setHeader(HttpHeader.EXPIRES, null);
    setHeader(HttpHeader.LAST_MODIFIED, null);
    setHeader(HttpHeader.CACHE_CONTROL, null);
    setHeader(HttpHeader.CONTENT_TYPE, null);
    setHeader(HttpHeader.CONTENT_LENGTH, null);

    _outputType = OutputType.NONE;
    setStatus(code);
    _reason = message;

    Request request = _channel.getRequest();
    Throwable cause = (Throwable) request.getAttribute(Dispatcher.ERROR_EXCEPTION);
    if (message == null) message = cause == null ? HttpStatus.getMessage(code) : cause.toString();

    // If we are allowed to have a body
    if (code != SC_NO_CONTENT
        && code != SC_NOT_MODIFIED
        && code != SC_PARTIAL_CONTENT
        && code >= SC_OK) {

      ErrorHandler error_handler = null;
      ContextHandler.Context context = request.getContext();
      if (context != null) error_handler = context.getContextHandler().getErrorHandler();
      if (error_handler == null) error_handler = _channel.getServer().getBean(ErrorHandler.class);
      if (error_handler != null) {
        request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, new Integer(code));
        request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
        request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
        request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, request.getServletName());
        error_handler.handle(null, _channel.getRequest(), _channel.getRequest(), this);
      } else {
        setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
        setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString());
        ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(2048);
        if (message != null) {
          message = StringUtil.replace(message, "&", "&amp;");
          message = StringUtil.replace(message, "<", "&lt;");
          message = StringUtil.replace(message, ">", "&gt;");
        }
        String uri = request.getRequestURI();
        if (uri != null) {
          uri = StringUtil.replace(uri, "&", "&amp;");
          uri = StringUtil.replace(uri, "<", "&lt;");
          uri = StringUtil.replace(uri, ">", "&gt;");
        }

        writer.write(
            "<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
        writer.write("<title>Error ");
        writer.write(Integer.toString(code));
        writer.write(' ');
        if (message == null) writer.write(message);
        writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
        writer.write(Integer.toString(code));
        writer.write("</h2>\n<p>Problem accessing ");
        writer.write(uri);
        writer.write(". Reason:\n<pre>    ");
        writer.write(message);
        writer.write("</pre>");
        writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
        writer.write("\n</body>\n</html>\n");

        writer.flush();
        setContentLength(writer.size());
        writer.writeTo(getOutputStream());
        writer.destroy();
      }
    } else if (code != SC_PARTIAL_CONTENT) {
      // TODO work out why this is required?
      _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE);
      _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH);
      _characterEncoding = null;
      _mimeType = null;
    }

    complete();
  }
예제 #22
0
 public void writeRaw(String str) throws IOException {
   LOG.debug("write((String)[{}]){}{})", str.length(), '\n', str);
   out.write(StringUtil.getBytes(str, StringUtil.__ISO_8859_1));
 }
예제 #23
0
  protected String processFlowFile(
      final ComponentLog logger,
      final DebugLevels logLevel,
      final FlowFile flowFile,
      final ProcessSession session,
      final ProcessContext context) {
    final Set<String> attributeKeys =
        getAttributesToLog(flowFile.getAttributes().keySet(), context);
    final ComponentLog LOG = getLogger();
    final String dashedLine;

    String logPrefix =
        context.getProperty(LOG_PREFIX).evaluateAttributeExpressions(flowFile).getValue();

    if (StringUtil.isBlank(logPrefix)) {
      dashedLine = StringUtils.repeat('-', 50);
    } else {
      // abbreviate long lines
      logPrefix = StringUtils.abbreviate(logPrefix, 40);
      // center the logPrefix and pad with dashes
      logPrefix = StringUtils.center(logPrefix, 40, '-');
      // five dashes on the left and right side, plus the dashed logPrefix
      dashedLine = StringUtils.repeat('-', 5) + logPrefix + StringUtils.repeat('-', 5);
    }

    // Pretty print metadata
    final StringBuilder message = new StringBuilder();
    message.append("logging for flow file ").append(flowFile);
    message.append("\n");
    message.append(dashedLine);
    message.append("\nStandard FlowFile Attributes");
    message.append(
        String.format(
            "\nKey: '%1$s'\n\tValue: '%2$s'", "entryDate", new Date(flowFile.getEntryDate())));
    message.append(
        String.format(
            "\nKey: '%1$s'\n\tValue: '%2$s'",
            "lineageStartDate", new Date(flowFile.getLineageStartDate())));
    message.append(String.format("\nKey: '%1$s'\n\tValue: '%2$s'", "fileSize", flowFile.getSize()));
    message.append("\nFlowFile Attribute Map Content");
    for (final String key : attributeKeys) {
      message.append(
          String.format("\nKey: '%1$s'\n\tValue: '%2$s'", key, flowFile.getAttribute(key)));
    }
    message.append("\n");
    message.append(dashedLine);

    // The user can request to log the payload
    final boolean logPayload = context.getProperty(LOG_PAYLOAD).asBoolean();
    if (logPayload) {
      message.append("\n");
      if (flowFile.getSize() < ONE_MB) {
        final FlowFilePayloadCallback callback = new FlowFilePayloadCallback();
        session.read(flowFile, callback);
        message.append(callback.getContents());
      } else {
        message.append("\n Not including payload since it is larger than one mb.");
      }
    }
    final String outputMessage = message.toString().trim();
    // Uses optional property to specify logging level
    switch (logLevel) {
      case info:
        LOG.info(outputMessage);
        break;
      case debug:
        LOG.debug(outputMessage);
        break;
      case warn:
        LOG.warn(outputMessage);
        break;
      case trace:
        LOG.trace(outputMessage);
        break;
      case error:
        LOG.error(outputMessage);
        break;
      default:
        LOG.debug(outputMessage);
    }

    return outputMessage;
  }