/** * Handles writing out the header data. It can also take a byte buffer of user data, to enable * both user data and headers to be written out in a single operation, which has a noticeable * performance impact. * * <p>It is up to the caller to note the current position of this buffer before and after they * call this method, and use this to figure out how many bytes (if any) have been written. * * @param state * @param userData * @return * @throws java.io.IOException */ private int processWrite(int state, final ByteBuffer userData) throws IOException { if (state == STATE_START) { pooledBuffer = pool.allocate(); } ClientRequest request = this.request; ByteBuffer buffer = pooledBuffer.getResource(); Iterator<HttpString> nameIterator = this.nameIterator; Iterator<String> valueIterator = this.valueIterator; int charIndex = this.charIndex; int length; String string = this.string; HttpString headerName = this.headerName; int res; // BUFFER IS FLIPPED COMING IN if (state != STATE_START && buffer.hasRemaining()) { log.trace("Flushing remaining buffer"); do { res = next.write(buffer); if (res == 0) { return state; } } while (buffer.hasRemaining()); } buffer.clear(); // BUFFER IS NOW EMPTY FOR FILLING for (; ; ) { switch (state) { case STATE_BODY: { // shouldn't be possible, but might as well do the right thing anyway return state; } case STATE_START: { log.trace("Starting request"); // we assume that our buffer has enough space for the initial request line plus one more // CR+LF assert buffer.remaining() >= 50; request.getMethod().appendTo(buffer); buffer.put((byte) ' '); string = request.getPath(); length = string.length(); for (charIndex = 0; charIndex < length; charIndex++) { buffer.put((byte) string.charAt(charIndex)); } buffer.put((byte) ' '); request.getProtocol().appendTo(buffer); buffer.put((byte) '\r').put((byte) '\n'); HeaderMap headers = request.getRequestHeaders(); nameIterator = headers.getHeaderNames().iterator(); if (!nameIterator.hasNext()) { log.trace("No request headers"); buffer.put((byte) '\r').put((byte) '\n'); buffer.flip(); while (buffer.hasRemaining()) { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_BUF_FLUSH; } } pooledBuffer.free(); pooledBuffer = null; log.trace("Body"); return STATE_BODY; } headerName = nameIterator.next(); charIndex = 0; // fall thru } case STATE_HDR_NAME: { log.tracef("Processing header '%s'", headerName); length = headerName.length(); while (charIndex < length) { if (buffer.hasRemaining()) { buffer.put(headerName.byteAt(charIndex++)); } else { log.trace("Buffer flush"); buffer.flip(); do { res = next.write(buffer); if (res == 0) { this.string = string; this.headerName = headerName; this.charIndex = charIndex; this.valueIterator = valueIterator; this.nameIterator = nameIterator; log.trace("Continuation"); return STATE_HDR_NAME; } } while (buffer.hasRemaining()); buffer.clear(); } } // fall thru } case STATE_HDR_D: { if (!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); this.string = string; this.headerName = headerName; this.charIndex = charIndex; this.valueIterator = valueIterator; this.nameIterator = nameIterator; return STATE_HDR_D; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) ':'); // fall thru } case STATE_HDR_DS: { if (!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); this.string = string; this.headerName = headerName; this.charIndex = charIndex; this.valueIterator = valueIterator; this.nameIterator = nameIterator; return STATE_HDR_DS; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) ' '); if (valueIterator == null) { valueIterator = request.getRequestHeaders().get(headerName).iterator(); } assert valueIterator.hasNext(); string = valueIterator.next(); charIndex = 0; // fall thru } case STATE_HDR_VAL: { log.tracef("Processing header value '%s'", string); length = string.length(); while (charIndex < length) { if (buffer.hasRemaining()) { buffer.put((byte) string.charAt(charIndex++)); } else { buffer.flip(); do { res = next.write(buffer); if (res == 0) { this.string = string; this.headerName = headerName; this.charIndex = charIndex; this.valueIterator = valueIterator; this.nameIterator = nameIterator; log.trace("Continuation"); return STATE_HDR_VAL; } } while (buffer.hasRemaining()); buffer.clear(); } } charIndex = 0; if (!valueIterator.hasNext()) { if (!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_EOL_CR; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 13); // CR if (!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_EOL_LF; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 10); // LF if (nameIterator.hasNext()) { headerName = nameIterator.next(); valueIterator = null; state = STATE_HDR_NAME; break; } else { if (!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_FINAL_CR; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 13); // CR if (!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_FINAL_LF; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 10); // LF this.nameIterator = null; this.valueIterator = null; this.string = null; buffer.flip(); // for performance reasons we use a gather write if there is user data if (userData == null) { do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } else { ByteBuffer[] b = {buffer, userData}; do { long r = next.write(b, 0, b.length); if (r == 0 && buffer.hasRemaining()) { log.trace("Continuation"); return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } pooledBuffer.free(); pooledBuffer = null; log.trace("Body"); return STATE_BODY; } // not reached } // fall thru } // Clean-up states case STATE_HDR_EOL_CR: { if (!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_EOL_CR; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 13); // CR } case STATE_HDR_EOL_LF: { if (!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_EOL_LF; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 10); // LF if (valueIterator.hasNext()) { state = STATE_HDR_NAME; break; } else if (nameIterator.hasNext()) { headerName = nameIterator.next(); valueIterator = null; state = STATE_HDR_NAME; break; } // fall thru } case STATE_HDR_FINAL_CR: { if (!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_FINAL_CR; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 13); // CR // fall thru } case STATE_HDR_FINAL_LF: { if (!buffer.hasRemaining()) { buffer.flip(); do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_HDR_FINAL_LF; } } while (buffer.hasRemaining()); buffer.clear(); } buffer.put((byte) 10); // LF this.nameIterator = null; this.valueIterator = null; this.string = null; buffer.flip(); // for performance reasons we use a gather write if there is user data if (userData == null) { do { res = next.write(buffer); if (res == 0) { log.trace("Continuation"); return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } else { ByteBuffer[] b = {buffer, userData}; do { long r = next.write(b, 0, b.length); if (r == 0 && buffer.hasRemaining()) { log.trace("Continuation"); return STATE_BUF_FLUSH; } } while (buffer.hasRemaining()); } // fall thru } case STATE_BUF_FLUSH: { // buffer was successfully flushed above pooledBuffer.free(); pooledBuffer = null; return STATE_BODY; } default: { throw new IllegalStateException(); } } } }
@Override public void sendRequest(ClientRequest request, ClientCallback<ClientExchange> clientCallback) { request.getRequestHeaders().put(PATH, request.getPath()); request.getRequestHeaders().put(SCHEME, "https"); request.getRequestHeaders().put(VERSION, request.getProtocol().toString()); request.getRequestHeaders().put(METHOD, request.getMethod().toString()); request.getRequestHeaders().put(HOST, request.getRequestHeaders().getFirst(Headers.HOST)); request.getRequestHeaders().remove(Headers.HOST); SpdySynStreamStreamSinkChannel sinkChannel; try { sinkChannel = spdyChannel.createStream(request.getRequestHeaders()); } catch (IOException e) { clientCallback.failed(e); return; } SpdyClientExchange exchange = new SpdyClientExchange(this, sinkChannel, request); currentExchanges.put(sinkChannel.getStreamId(), exchange); boolean hasContent = true; String fixedLengthString = request.getRequestHeaders().getFirst(CONTENT_LENGTH); String transferEncodingString = request.getRequestHeaders().getLast(TRANSFER_ENCODING); if (fixedLengthString != null) { try { long length = Long.parseLong(fixedLengthString); hasContent = length != 0; } catch (NumberFormatException e) { handleError(new IOException(e)); return; } } else if (transferEncodingString == null) { hasContent = false; } if (clientCallback != null) { clientCallback.completed(exchange); } if (!hasContent) { // if there is no content we flush the response channel. // otherwise it is up to the user try { sinkChannel.shutdownWrites(); if (!sinkChannel.flush()) { sinkChannel .getWriteSetter() .set( ChannelListeners.flushingChannelListener( null, new ChannelExceptionHandler<StreamSinkChannel>() { @Override public void handleException( StreamSinkChannel channel, IOException exception) { handleError(exception); } })); sinkChannel.resumeWrites(); } } catch (IOException e) { handleError(e); } } else if (!sinkChannel.isWriteResumed()) { try { // TODO: this needs some more thought if (!sinkChannel.flush()) { sinkChannel .getWriteSetter() .set( new ChannelListener<StreamSinkChannel>() { @Override public void handleEvent(StreamSinkChannel channel) { try { if (channel.flush()) { channel.suspendWrites(); } } catch (IOException e) { handleError(e); } } }); sinkChannel.resumeWrites(); } } catch (IOException e) { handleError(e); } } }