private synchronized int flushBuffer() throws IOException { if (!_endp.isOpen()) throw new EofException(); if (_buffer != null && _buffer.hasContent()) return _endp.flush(_buffer); return 0; }
public void receive() { EndPoint endPoint = connection.getEndPoint(); HttpClient client = connection.getHttpClient(); ByteBufferPool bufferPool = client.getByteBufferPool(); ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true); try { while (true) { // Connection may be closed in a parser callback if (connection.isClosed()) { LOG.debug("{} closed", connection); break; } else { int read = endPoint.fill(buffer); LOG.debug("Read {} bytes from {}", read, connection); if (read > 0) { parse(buffer); } else if (read == 0) { fillInterested(); break; } else { shutdown(); break; } } } } catch (EofException x) { LOG.ignore(x); failAndClose(x); } catch (Exception x) { LOG.debug(x); failAndClose(x); } finally { bufferPool.release(buffer); } }
private void replaceConnection() { EndPoint endPoint = getEndPoint(); Connection connection = client.getConnectionFactory().newConnection(channel, endPoint, attachment); endPoint.getConnection().onClose(); endPoint.setConnection(connection); connection.onOpen(); }
private void upgradeConnection(ClientUpgradeResponse response) { EndPoint endp = getEndPoint(); Executor executor = getExecutor(); WebSocketClientConnection connection = new WebSocketClientConnection(endp, executor, client); // Initialize / Negotiate Extensions EventDriver websocket = client.getWebSocket(); WebSocketPolicy policy = client.getPolicy(); String acceptedSubProtocol = response.getAcceptedSubProtocol(); WebSocketSession session = new WebSocketSession(request.getRequestURI(), websocket, connection); session.setPolicy(policy); session.setNegotiatedSubprotocol(acceptedSubProtocol); connection.setSession(session); List<Extension> extensions = client.getFactory().initExtensions(response.getExtensions()); // Start with default routing. IncomingFrames incoming = session; // OutgoingFrames outgoing = connection; // Connect extensions if (extensions != null) { connection.getParser().configureFromExtensions(extensions); connection.getGenerator().configureFromExtensions(extensions); // FIXME // Iterator<Extension> extIter; // // Connect outgoings // extIter = extensions.iterator(); // while (extIter.hasNext()) // { // Extension ext = extIter.next(); // ext.setNextOutgoingFrames(outgoing); // outgoing = ext; // } // // // Connect incomings // Collections.reverse(extensions); // extIter = extensions.iterator(); // while (extIter.hasNext()) // { // Extension ext = extIter.next(); // ext.setNextIncomingFrames(incoming); // incoming = ext; // } } // configure session for outgoing flows // session.setOutgoing(outgoing); // configure connection for incoming flows connection.getParser().setIncomingFramesHandler(incoming); // Now swap out the connection endp.setConnection(connection); connection.onOpen(); }
private int fill(EndPoint endPoint, ByteBuffer buffer) { try { if (endPoint.isInputShutdown()) return -1; return endPoint.fill(buffer); } catch (IOException x) { endPoint.close(); throw new RuntimeIOException(x); } }
public void disconnect(boolean onlyOutput) { EndPoint endPoint = getEndPoint(); // We need to gently close first, to allow // SSL close alerts to be sent by Jetty LOG.debug("Shutting down output {}", endPoint); endPoint.shutdownOutput(); if (!onlyOutput) { LOG.debug("Closing {}", endPoint); endPoint.close(); } }
@Override protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback) { try { HttpClient client = getHttpChannel().getHttpDestination().getHttpClient(); ByteBufferPool bufferPool = client.getByteBufferPool(); ByteBuffer chunk = null; while (true) { ByteBuffer contentBuffer = content.getByteBuffer(); boolean lastContent = content.isLast(); HttpGenerator.Result result = generator.generateRequest(null, null, chunk, contentBuffer, lastContent); switch (result) { case NEED_CHUNK: { chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false); break; } case FLUSH: { EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint(); if (chunk != null) endPoint.write( new ByteBufferRecyclerCallback(callback, bufferPool, chunk), chunk, contentBuffer); else endPoint.write(callback, contentBuffer); return; } case SHUTDOWN_OUT: { shutdownOutput(); break; } case CONTINUE: { break; } case DONE: { assert generator.isEnd(); callback.succeeded(); return; } default: { throw new IllegalStateException(); } } } } catch (Exception x) { LOG.debug(x); callback.failed(x); } }
@Override protected void send(HttpExchange exchange) { Request request = exchange.getRequest(); normalizeRequest(request); // Save the old idle timeout to restore it EndPoint endPoint = getEndPoint(); idleTimeout = endPoint.getIdleTimeout(); endPoint.setIdleTimeout(request.getIdleTimeout()); // One channel per connection, just delegate the send if (channel.associate(exchange)) channel.send(); else channel.release(); }
private synchronized int expelBuffer(long blockFor) throws IOException { if (_buffer == null) return 0; int result = flushBuffer(); _buffer.compact(); if (!_endp.isBlocking()) { while (_buffer.space() == 0) { boolean ready = _endp.blockWritable(blockFor); if (!ready) throw new IOException("Write timeout"); result += flushBuffer(); _buffer.compact(); } } return result; }
@Override public Connection newConnection(Connector connector, EndPoint endPoint) { ServerSessionListener listener = newSessionListener(connector, endPoint); Generator generator = new Generator(connector.getByteBufferPool(), getMaxHeaderTableSize()); HTTP2ServerSession session = new HTTP2ServerSession( connector.getScheduler(), endPoint, generator, listener, new HTTP2FlowControl(getInitialStreamWindow())); session.setMaxLocalStreams(getMaxConcurrentStreams()); session.setMaxRemoteStreams(getMaxConcurrentStreams()); long idleTimeout = endPoint.getIdleTimeout(); if (idleTimeout > 0) idleTimeout /= 2; session.setStreamIdleTimeout(idleTimeout); Parser parser = newServerParser(connector.getByteBufferPool(), session); HTTP2Connection connection = new HTTP2ServerConnection( connector.getByteBufferPool(), connector.getExecutor(), endPoint, parser, session, getInputBufferSize(), listener); return configure(connection, connector, endPoint); }
/** * This method is invoked when the idle timeout triggers. We check the close state to act * appropriately: * * <p>* NOT_CLOSED: it's a real idle timeout, we just initiate a close, see {@link #close(int, * String, Callback)}. * * <p>* LOCALLY_CLOSED: we have sent a GO_AWAY and only shutdown the output, but the other peer * did not close the connection so we never received the TCP FIN, and therefore we terminate. * * <p>* REMOTELY_CLOSED: the other peer sent us a GO_AWAY, we should have queued a disconnect, but * for some reason it was not processed (for example, queue was stuck because of TCP congestion), * therefore we terminate. See {@link #onGoAway(GoAwayFrame)}. * * @return true if the session should be closed, false otherwise * @see #onGoAway(GoAwayFrame) * @see #close(int, String, Callback) * @see #onShutdown() */ @Override public boolean onIdleTimeout() { switch (closed.get()) { case NOT_CLOSED: { long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - idleTime); if (elapsed < endPoint.getIdleTimeout()) return false; return notifyIdleTimeout(this); } case LOCALLY_CLOSED: case REMOTELY_CLOSED: { abort(new TimeoutException("Idle timeout " + endPoint.getIdleTimeout() + " ms")); return false; } default: { return false; } } }
/** * Read / Parse the waiting read/fill buffer * * @param buffer the buffer to fill into from the endpoint * @return true if there is more to read, false if reading should stop */ private boolean read(ByteBuffer buffer) { EndPoint endPoint = getEndPoint(); try { while (true) { int filled = endPoint.fill(buffer); if (filled == 0) { return true; } else if (filled < 0) { LOG.debug("read - EOF Reached"); return false; } else { if (LOG.isDebugEnabled()) { LOG.debug("Filled {} bytes - {}", filled, BufferUtil.toDetailString(buffer)); } ClientUpgradeResponse resp = parser.parse(buffer); if (resp != null) { // Got a response! client.setUpgradeResponse(resp); validateResponse(resp); notifyConnect(resp); upgradeConnection(resp); return false; // do no more reading } } } } catch (IOException e) { LOG.warn(e); client.failed(e); disconnect(false); return false; } catch (UpgradeException e) { LOG.warn(e); client.failed(e); disconnect(false); return false; } }
public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException { long blockFor = _endp.getMaxIdleTime(); if (_buffer == null) _buffer = _buffers.getDirectBuffer(); if (_buffer.space() == 0) expelBuffer(blockFor); bufferPut(opcode, blockFor); if (isLengthFrame(opcode)) { // Send a length delimited frame // How many bytes we need for the length ? // We have 7 bits available, so log2(length) / 7 + 1 // For example, 50000 bytes is 2 8-bytes: 11000011 01010000 // but we need to write it in 3 7-bytes 0000011 0000110 1010000 // 65536 == 1 00000000 00000000 => 100 0000000 0000000 int lengthBytes = new BigInteger(String.valueOf(length)).bitLength() / 7 + 1; for (int i = lengthBytes - 1; i > 0; --i) { byte lengthByte = (byte) (0x80 | (0x7F & (length >> 7 * i))); bufferPut(lengthByte, blockFor); } bufferPut((byte) (0x7F & length), blockFor); } int remaining = length; while (remaining > 0) { int chunk = remaining < _buffer.space() ? remaining : _buffer.space(); _buffer.put(content, offset + (length - remaining), chunk); remaining -= chunk; if (_buffer.space() > 0) { if (!isLengthFrame(opcode)) _buffer.put((byte) 0xFF); // Gently flush the data, issuing a non-blocking write flushBuffer(); } else { // Forcibly flush the data, issuing a blocking write expelBuffer(blockFor); if (remaining == 0) { if (!isLengthFrame(opcode)) _buffer.put((byte) 0xFF); // Gently flush the data, issuing a non-blocking write flushBuffer(); } } } }
public HTTP2Session( Scheduler scheduler, EndPoint endPoint, Generator generator, Session.Listener listener, FlowControlStrategy flowControl, int initialStreamId) { this.scheduler = scheduler; this.endPoint = endPoint; this.generator = generator; this.listener = listener; this.flowControl = flowControl; this.flusher = new HTTP2Flusher(this); this.maxLocalStreams = -1; this.maxRemoteStreams = -1; this.streamIds.set(initialStreamId); this.streamIdleTimeout = endPoint.getIdleTimeout(); this.sendWindow.set(FlowControlStrategy.DEFAULT_WINDOW_SIZE); this.recvWindow.set(FlowControlStrategy.DEFAULT_WINDOW_SIZE); this.pushEnabled = true; // SPEC: by default, push is enabled. this.idleTime = System.nanoTime(); }
private void tunnelSucceeded() { try { // Replace the promise back with the original context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise); HttpDestination destination = (HttpDestination) context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY); HttpClient client = destination.getHttpClient(); ClientConnectionFactory sslConnectionFactory = new SslClientConnectionFactory( client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory); org.eclipse.jetty.io.Connection oldConnection = endPoint.getConnection(); org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context); Helper.replaceConnection(oldConnection, newConnection); LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection); } catch (Throwable x) { tunnelFailed(x); } }
@Override public void write(ByteBuffer buffer, final Callback callback) { EndPoint endPoint = getEndPoint(); endPoint.write(callback, buffer); }
public void disconnect() { if (LOG.isDebugEnabled()) LOG.debug("Disconnecting {}", this); endPoint.close(); }
public InetSocketAddress getLocalAddress() { return _endPoint.getLocalAddress(); }
@Test public void testMaxIdleWithRequest11NoClientClose() throws Exception { final Exchanger<EndPoint> exchanger = new Exchanger<>(); configureServer( new EchoHandler() { @Override public void handle( String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { try { exchanger.exchange(baseRequest.getHttpChannel().getEndPoint()); } catch (Exception e) { e.printStackTrace(); } super.handle(target, baseRequest, request, response); } }); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); assertFalse(client.isClosed()); OutputStream os = client.getOutputStream(); InputStream is = client.getInputStream(); String content = "Wibble"; byte[] contentB = content.getBytes("utf-8"); os.write( ("POST /echo HTTP/1.1\r\n" + "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" + "content-type: text/plain; charset=utf-8\r\n" + "content-length: " + contentB.length + "\r\n" + "connection: close\r\n" + "\r\n") .getBytes("utf-8")); os.write(contentB); os.flush(); // Get the server side endpoint EndPoint endPoint = exchanger.exchange(null, 10, TimeUnit.SECONDS); // read the response IO.toString(is); // check client reads EOF assertEquals(-1, is.read()); TimeUnit.MILLISECONDS.sleep(3 * MAX_IDLE_TIME); // further writes will get broken pipe or similar try { for (int i = 0; i < 1000; i++) { os.write( ("GET / HTTP/1.0\r\n" + "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" + "connection: keep-alive\r\n" + "\r\n") .getBytes("utf-8")); os.flush(); } Assert.fail("half close should have timed out"); } catch (SocketException e) { // expected } // check the server side is closed Assert.assertFalse(endPoint.isOpen()); }
private void tunnelFailed(Throwable failure) { endPoint.close(); failed(failure); }
public InetSocketAddress getRemoteAddress() { return _endPoint.getRemoteAddress(); }
@Override protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback) { Request request = exchange.getRequest(); ContentProvider requestContent = request.getContent(); long contentLength = requestContent == null ? -1 : requestContent.getLength(); String path = request.getPath(); String query = request.getQuery(); if (query != null) path += "?" + query; HttpGenerator.RequestInfo requestInfo = new HttpGenerator.RequestInfo( request.getVersion(), request.getHeaders(), contentLength, request.getMethod(), path); try { HttpClient client = getHttpChannel().getHttpDestination().getHttpClient(); ByteBufferPool bufferPool = client.getByteBufferPool(); ByteBuffer header = bufferPool.acquire(client.getRequestBufferSize(), false); ByteBuffer chunk = null; ByteBuffer contentBuffer = null; boolean lastContent = false; if (!expects100Continue(request)) { content.advance(); contentBuffer = content.getByteBuffer(); lastContent = content.isLast(); } while (true) { HttpGenerator.Result result = generator.generateRequest(requestInfo, header, chunk, contentBuffer, lastContent); switch (result) { case NEED_CHUNK: { chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false); break; } case FLUSH: { int size = 1; boolean hasChunk = chunk != null; if (hasChunk) ++size; boolean hasContent = contentBuffer != null; if (hasContent) ++size; ByteBuffer[] toWrite = new ByteBuffer[size]; ByteBuffer[] toRecycle = new ByteBuffer[hasChunk ? 2 : 1]; toWrite[0] = header; toRecycle[0] = header; if (hasChunk) { toWrite[1] = chunk; toRecycle[1] = chunk; } if (hasContent) toWrite[toWrite.length - 1] = contentBuffer; EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint(); endPoint.write( new ByteBufferRecyclerCallback(callback, bufferPool, toRecycle), toWrite); return; } default: { throw new IllegalStateException(); } } } } catch (Exception x) { LOG.debug(x); callback.failed(x); } }
public boolean isDisconnected() { return !endPoint.isOpen(); }
@Override public InetSocketAddress getLocalAddress() { return endPoint.getLocalAddress(); }
@Test public void testMaxIdleWithRequest10ClientIgnoresClose() throws Exception { final Exchanger<EndPoint> exchanger = new Exchanger<>(); configureServer( new HelloWorldHandler() { @Override public void handle( String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { try { exchanger.exchange(baseRequest.getHttpChannel().getEndPoint()); } catch (Exception e) { e.printStackTrace(); } super.handle(target, baseRequest, request, response); } }); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); assertFalse(client.isClosed()); OutputStream os = client.getOutputStream(); InputStream is = client.getInputStream(); os.write( ("GET / HTTP/1.0\r\n" + "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" + "connection: close\r\n" + "\r\n") .getBytes("utf-8")); os.flush(); // Get the server side endpoint EndPoint endPoint = exchanger.exchange(null, 10, TimeUnit.SECONDS); if (endPoint instanceof SslConnection.DecryptedEndPoint) endPoint = endPoint.getConnection().getEndPoint(); // read the response String result = IO.toString(is); Assert.assertThat("OK", result, containsString("200 OK")); // check client reads EOF assertEquals(-1, is.read()); assertTrue(endPoint.isOutputShutdown()); Thread.sleep(2 * MAX_IDLE_TIME); // further writes will get broken pipe or similar try { long end = System.currentTimeMillis() + MAX_IDLE_TIME + 3000; while (System.currentTimeMillis() < end) { os.write("THIS DATA SHOULD NOT BE PARSED!\n\n".getBytes("utf-8")); os.flush(); Thread.sleep(100); } Assert.fail("half close should have timed out"); } catch (SocketException e) { // expected // Give the SSL onClose time to act Thread.sleep(100); } // check the server side is closed Assert.assertFalse(endPoint.isOpen()); }
@Override public InetSocketAddress getRemoteAddress() { return endPoint.getRemoteAddress(); }
@Override protected Connection onSwitchProtocol(EndPoint endp) throws IOException { _log.debug("onSwitchProtocol: host {}", (endp == null) ? null : endp.getRemoteHost()); return super.onSwitchProtocol(endp); }
public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException { // System.err.printf("<< %s %s // %s\n",TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),length); long blockFor = _endp.getMaxIdleTime(); if (_buffer == null) _buffer = (_maskGen != null) ? _buffers.getBuffer() : _buffers.getDirectBuffer(); boolean last = WebSocketConnectionD06.isLastFrame(flags); opcode = (byte) (((0xf & flags) << 4) + 0xf & opcode); int space = (_maskGen != null) ? 14 : 10; do { opcode = _opsent ? WebSocketConnectionD06.OP_CONTINUATION : opcode; _opsent = true; int payload = length; if (payload + space > _buffer.capacity()) { // We must fragement, so clear FIN bit opcode &= (byte) 0x7F; // Clear the FIN bit payload = _buffer.capacity() - space; } else if (last) opcode |= (byte) 0x80; // Set the FIN bit // ensure there is space for header if (_buffer.space() <= space) expelBuffer(blockFor); // write mask if ((_maskGen != null)) { _maskGen.genMask(_mask); _m = 0; _buffer.put(_mask); } // write the opcode and length if (payload > 0xffff) { bufferPut( new byte[] { opcode, (byte) 0x7f, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) ((payload >> 24) & 0xff), (byte) ((payload >> 16) & 0xff), (byte) ((payload >> 8) & 0xff), (byte) (payload & 0xff) }); } else if (payload >= 0x7e) { bufferPut(new byte[] {opcode, (byte) 0x7e, (byte) (payload >> 8), (byte) (payload & 0xff)}); } else { bufferPut(opcode); bufferPut((byte) payload); } // write payload int remaining = payload; while (remaining > 0) { _buffer.compact(); int chunk = remaining < _buffer.space() ? remaining : _buffer.space(); if ((_maskGen != null)) { for (int i = 0; i < chunk; i++) bufferPut(content[offset + (payload - remaining) + i]); } else _buffer.put(content, offset + (payload - remaining), chunk); remaining -= chunk; if (_buffer.space() > 0) { // Gently flush the data, issuing a non-blocking write flushBuffer(); } else { // Forcibly flush the data, issuing a blocking write expelBuffer(blockFor); if (remaining == 0) { // Gently flush the data, issuing a non-blocking write flushBuffer(); } } } offset += payload; length -= payload; } while (length > 0); _opsent = !last; }