void receiveHeaders(List<String> headers, HeadersMode headersMode) { assert (!Thread.holdsLock(SpdyStream.this)); ErrorCode errorCode = null; boolean open = true; synchronized (this) { if (responseHeaders == null) { if (headersMode.failIfHeadersAbsent()) { errorCode = ErrorCode.PROTOCOL_ERROR; } else { responseHeaders = headers; open = isOpen(); notifyAll(); } } else { if (headersMode.failIfHeadersPresent()) { errorCode = ErrorCode.STREAM_IN_USE; } else { List<String> newHeaders = new ArrayList<String>(); newHeaders.addAll(responseHeaders); newHeaders.addAll(headers); this.responseHeaders = newHeaders; } } } if (errorCode != null) { closeLater(errorCode); } else if (!open) { connection.removeStream(id); } }
void receiveFin() { assert (!Thread.holdsLock(SpdyStream.this)); boolean open; synchronized (this) { this.in.finished = true; open = isOpen(); notifyAll(); } if (!open) { connection.removeStream(id); } }
/** Returns true if this stream was closed. */ private boolean closeInternal(ErrorCode errorCode) { assert (!Thread.holdsLock(this)); synchronized (this) { if (this.errorCode != null) { return false; } if (in.finished && out.finished) { return false; } this.errorCode = errorCode; notifyAll(); } connection.removeStream(id); return true; }
/** Create an {@code SSLSocket} and perform the TLS handshake and certificate validation. */ private void upgradeToTls(TunnelRequest tunnelRequest) throws IOException { Platform platform = Platform.get(); // Make an SSL Tunnel on the first message pair of each SSL + proxy connection. if (requiresTunnel()) { makeTunnel(tunnelRequest); } // Create the wrapper over connected socket. socket = route.address.sslSocketFactory.createSocket( socket, route.address.uriHost, route.address.uriPort, true /* autoClose */); SSLSocket sslSocket = (SSLSocket) socket; if (route.modernTls) { platform.enableTlsExtensions(sslSocket, route.address.uriHost); } else { platform.supportTlsIntolerantServer(sslSocket); } boolean useNpn = route.modernTls && route.address.transports.contains("spdy/3"); if (useNpn) { platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS); } // Force handshake. This can throw! sslSocket.startHandshake(); // Verify that the socket's certificates are acceptable for the target host. if (!route.address.hostnameVerifier.verify(route.address.uriHost, sslSocket.getSession())) { throw new IOException("Hostname '" + route.address.uriHost + "' was not verified"); } out = sslSocket.getOutputStream(); in = sslSocket.getInputStream(); byte[] selectedProtocol; if (useNpn && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) { if (Arrays.equals(selectedProtocol, SPDY3)) { sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream. spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out).build(); spdyConnection.sendConnectionHeader(); } else if (!Arrays.equals(selectedProtocol, HTTP_11)) { throw new IOException( "Unexpected NPN transport " + new String(selectedProtocol, "ISO-8859-1")); } } }
private void cancelStreamIfNecessary() throws IOException { assert (!Thread.holdsLock(SpdyStream.this)); boolean open; boolean cancel; synchronized (this) { cancel = !in.finished && in.closed && (out.finished || out.closed); open = isOpen(); } if (cancel) { // RST this stream to prevent additional data from being sent. This // is safe because the input stream is closed (we won't use any // further bytes) and the output stream is either finished or closed // (so RSTing both streams doesn't cause harm). SpdyStream.this.close(ErrorCode.CANCEL); } else if (!open) { connection.removeStream(id); } }
/** * Sends a reply to an incoming stream. * * @param out true to create an output stream that we can use to send data to the remote peer. * Corresponds to {@code FLAG_FIN}. */ public void reply(List<String> responseHeaders, boolean out) throws IOException { assert (!Thread.holdsLock(SpdyStream.this)); boolean outFinished = false; synchronized (this) { if (responseHeaders == null) { throw new NullPointerException("responseHeaders == null"); } if (isLocallyInitiated()) { throw new IllegalStateException("cannot reply to a locally initiated stream"); } if (this.responseHeaders != null) { throw new IllegalStateException("reply already sent"); } this.responseHeaders = responseHeaders; if (!out) { this.out.finished = true; outFinished = true; } } connection.writeSynReply(id, outFinished, responseHeaders); }
/** * Returns the time in ns when this connection became idle. Undefined if this connection is not * idle. */ public long getIdleStartTimeNs() { return spdyConnection == null ? idleStartTimeNs : spdyConnection.getIdleStartTimeNs(); }
/** Returns true if this connection is idle. */ public boolean isIdle() { return spdyConnection == null || spdyConnection.isIdle(); }
/** Create an {@code SSLSocket} and perform the TLS handshake and certificate validation. */ private void upgradeToTls(TunnelRequest tunnelRequest) throws IOException { Platform platform = Platform.get(); // Make an SSL Tunnel on the first message pair of each SSL + proxy connection. if (requiresTunnel()) { makeTunnel(tunnelRequest); } // Create the wrapper over connected socket. socket = route.address.sslSocketFactory.createSocket( socket, route.address.uriHost, route.address.uriPort, true /* autoClose */); SSLSocket sslSocket = (SSLSocket) socket; if (route.modernTls) { platform.enableTlsExtensions(sslSocket, route.address.uriHost); } else { platform.supportTlsIntolerantServer(sslSocket); } boolean useNpn = route.modernTls && ( // Contains a spdy variant. route.address.protocols.contains(Protocol.HTTP_2) || route.address.protocols.contains(Protocol.SPDY_3)); if (useNpn) { if (route.address.protocols.contains(Protocol.HTTP_2) // Contains both spdy variants. && route.address.protocols.contains(Protocol.SPDY_3)) { platform.setNpnProtocols(sslSocket, Protocol.HTTP2_SPDY3_AND_HTTP); } else if (route.address.protocols.contains(Protocol.HTTP_2)) { platform.setNpnProtocols(sslSocket, Protocol.HTTP2_AND_HTTP_11); } else { platform.setNpnProtocols(sslSocket, Protocol.SPDY3_AND_HTTP11); } } // Force handshake. This can throw! sslSocket.startHandshake(); // Verify that the socket's certificates are acceptable for the target host. if (!route.address.hostnameVerifier.verify(route.address.uriHost, sslSocket.getSession())) { throw new IOException("Hostname '" + route.address.uriHost + "' was not verified"); } out = sslSocket.getOutputStream(); in = sslSocket.getInputStream(); handshake = Handshake.get(sslSocket.getSession()); streamWrapper(); ByteString maybeProtocol; if (useNpn && (maybeProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) { Protocol selectedProtocol = Protocol.find(maybeProtocol); // Throws IOE on unknown. if (selectedProtocol.spdyVariant) { sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream. spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out) .protocol(selectedProtocol) .build(); spdyConnection.sendConnectionHeader(); } } }
/** * Abnormally terminate this stream. This enqueues a {@code RST_STREAM} frame and returns * immediately. */ public void closeLater(ErrorCode errorCode) { if (!closeInternal(errorCode)) { return; // Already closed. } connection.writeSynResetLater(id, errorCode); }
/** * Abnormally terminate this stream. This blocks until the {@code RST_STREAM} frame has been * transmitted. */ public void close(ErrorCode rstStatusCode) throws IOException { if (!closeInternal(rstStatusCode)) { return; // Already closed. } connection.writeSynReset(id, rstStatusCode); }