/** * Callers of this method are not thread safe, and sometimes on application threads. Most often, * this method will be called to send a buffer worth of data to the peer. * * <p>Writes are subject to the write window of the stream and the connection. Until there is a * window sufficient to send {@code byteCount}, the caller will block. For example, a user of * {@code HttpURLConnection} who flushes more bytes to the output stream than the connection's * write window will block. * * <p>Zero {@code byteCount} writes are not subject to flow control and will not block. The only * use case for zero {@code byteCount} is closing a flushed output stream. */ public void writeData(int streamId, boolean outFinished, Buffer buffer, long byteCount) throws IOException { if (byteCount == 0) { // Empty data frames are not flow-controlled. frameWriter.data(outFinished, streamId, buffer, 0); return; } while (byteCount > 0) { int toWrite; synchronized (FramedConnection.this) { try { while (bytesLeftInWriteWindow <= 0) { // Before blocking, confirm that the stream we're writing is still open. It's possible // that the stream has since been closed (such as if this write timed out.) if (!streams.containsKey(streamId)) { throw new IOException("stream closed"); } FramedConnection.this.wait(); // Wait until we receive a WINDOW_UPDATE. } } catch (InterruptedException e) { throw new InterruptedIOException(); } toWrite = (int) Math.min(byteCount, bytesLeftInWriteWindow); toWrite = Math.min(toWrite, frameWriter.maxDataLength()); bytesLeftInWriteWindow -= toWrite; } byteCount -= toWrite; frameWriter.data(outFinished && byteCount == 0, streamId, buffer, toWrite); } }
/** * Callers of this method are not thread safe, and sometimes on application threads. Most often, * this method will be called to send a buffer worth of data to the peer. * * <p>Writes are subject to the write window of the stream and the connection. Until there is a * window sufficient to send {@code byteCount}, the caller will block. For example, a user of * {@code HttpURLConnection} who flushes more bytes to the output stream than the connection's * write window will block. * * <p>Zero {@code byteCount} writes are not subject to flow control and will not block. The only * use case for zero {@code byteCount} is closing a flushed output stream. */ public void writeData(int streamId, boolean outFinished, OkBuffer buffer, long byteCount) throws IOException { if (byteCount == 0) { // Empty data frames are not flow-controlled. frameWriter.data(outFinished, streamId, buffer, 0); return; } while (byteCount > 0) { int toWrite; synchronized (SpdyConnection.this) { try { while (bytesLeftInWriteWindow <= 0) { SpdyConnection.this.wait(); // Wait until we receive a WINDOW_UPDATE. } } catch (InterruptedException e) { throw new InterruptedIOException(); } toWrite = (int) Math.min(Math.min(byteCount, bytesLeftInWriteWindow), maxFrameSize); bytesLeftInWriteWindow -= toWrite; } byteCount -= toWrite; frameWriter.data(outFinished && byteCount == 0, streamId, buffer, toWrite); } }
/** * @param sendConnectionPreface true to send connection preface frames. This should always be true * except for in tests that don't check for a connection preface. */ void start(boolean sendConnectionPreface) throws IOException { if (sendConnectionPreface) { frameWriter.connectionPreface(); frameWriter.settings(okHttpSettings); int windowSize = okHttpSettings.getInitialWindowSize(Settings.DEFAULT_INITIAL_WINDOW_SIZE); if (windowSize != Settings.DEFAULT_INITIAL_WINDOW_SIZE) { frameWriter.windowUpdate(0, windowSize - Settings.DEFAULT_INITIAL_WINDOW_SIZE); } } new Thread(readerRunnable).start(); // Not a daemon thread. }
private void writePing(boolean reply, int payload1, int payload2, Ping ping) throws IOException { synchronized (frameWriter) { // Observe the sent time immediately before performing I/O. if (ping != null) ping.send(); frameWriter.ping(reply, payload1, payload2); } }
/** * Called to perform a window update for this stream (or connection). Updates the window size * back to the size of the initial window and sends a window update frame to the remote * endpoint. */ void updateWindow(FrameWriter frameWriter) throws Http2Exception { // Expand the window for this stream back to the size of the initial window. int deltaWindowSize = initialWindowSize - windowSize(); addAndGet(deltaWindowSize); // Send a window update for the stream/connection. frameWriter.writeFrame(streamId, deltaWindowSize); }
private void close(ErrorCode connectionCode, ErrorCode streamCode) throws IOException { assert (!Thread.holdsLock(this)); IOException thrown = null; try { shutdown(connectionCode); } catch (IOException e) { thrown = e; } FramedStream[] streamsToClose = null; Ping[] pingsToCancel = null; synchronized (this) { if (!streams.isEmpty()) { streamsToClose = streams.values().toArray(new FramedStream[streams.size()]); streams.clear(); setIdle(false); } if (pings != null) { pingsToCancel = pings.values().toArray(new Ping[pings.size()]); pings = null; } } if (streamsToClose != null) { for (FramedStream stream : streamsToClose) { try { stream.close(streamCode); } catch (IOException e) { if (thrown != null) thrown = e; } } } if (pingsToCancel != null) { for (Ping ping : pingsToCancel) { ping.cancel(); } } // Close the writer to release its resources (such as deflaters). try { frameWriter.close(); } catch (IOException e) { if (thrown == null) thrown = e; } // Close the socket to break out the reader thread, which will clean up after itself. try { socket.close(); } catch (IOException e) { thrown = e; } if (thrown != null) throw thrown; }
/** Merges {@code settings} into this peer's settings and sends them to the remote peer. */ public void setSettings(Settings settings) throws IOException { synchronized (frameWriter) { synchronized (this) { if (shutdown) { throw new IOException("shutdown"); } okHttpSettings.merge(settings); frameWriter.settings(settings); } } }
private void close(ErrorCode connectionCode, ErrorCode streamCode) throws IOException { assert (!Thread.holdsLock(this)); IOException thrown = null; try { shutdown(connectionCode); } catch (IOException e) { thrown = e; } SpdyStream[] streamsToClose = null; Ping[] pingsToCancel = null; synchronized (this) { if (!streams.isEmpty()) { streamsToClose = streams.values().toArray(new SpdyStream[streams.size()]); streams.clear(); setIdle(false); } if (pings != null) { pingsToCancel = pings.values().toArray(new Ping[pings.size()]); pings = null; } } if (streamsToClose != null) { for (SpdyStream stream : streamsToClose) { try { stream.close(streamCode); } catch (IOException e) { if (thrown != null) thrown = e; } } } if (pingsToCancel != null) { for (Ping ping : pingsToCancel) { ping.cancel(); } } try { frameReader.close(); } catch (IOException e) { thrown = e; } try { frameWriter.close(); } catch (IOException e) { if (thrown == null) thrown = e; } if (thrown != null) throw thrown; }
private SpdyStream newStream( int associatedStreamId, List<Header> requestHeaders, boolean out, boolean in) throws IOException { boolean outFinished = !out; boolean inFinished = !in; int priority = -1; // TODO: permit the caller to specify a priority? int slot = 0; // TODO: permit the caller to specify a slot? SpdyStream stream; int streamId; synchronized (frameWriter) { synchronized (this) { if (shutdown) { throw new IOException("shutdown"); } streamId = nextStreamId; nextStreamId += 2; stream = new SpdyStream(streamId, this, outFinished, inFinished, priority, requestHeaders); if (stream.isOpen()) { streams.put(streamId, stream); setIdle(false); } } if (associatedStreamId == 0) { frameWriter.synStream( outFinished, inFinished, streamId, associatedStreamId, priority, slot, requestHeaders); } else if (client) { throw new IllegalArgumentException("client streams shouldn't have associated stream IDs"); } else { // HTTP/2 has a PUSH_PROMISE frame. frameWriter.pushPromise(associatedStreamId, streamId, requestHeaders); } } if (!out) { frameWriter.flush(); } return stream; }
/** * Called to perform a window update for this stream (or connection). Updates the window size * back to the size of the initial window and sends a window update frame to the remote * endpoint. */ public void updateWindow(FrameWriter frameWriter) throws Http2Exception { // Expand the window for this stream back to the size of the initial window. int deltaWindowSize = initialWindowSize - getSize(); addAndGet(deltaWindowSize); // Send a window update for the stream/connection. Http2WindowUpdateFrame updateFrame = new DefaultHttp2WindowUpdateFrame.Builder() .setStreamId(streamId) .setWindowSizeIncrement(deltaWindowSize) .build(); frameWriter.writeFrame(updateFrame); }
/** * Degrades this connection such that new streams can neither be created locally, nor accepted * from the remote peer. Existing streams are not impacted. This is intended to permit an endpoint * to gracefully stop accepting new requests without harming previously established streams. */ public void shutdown(ErrorCode statusCode) throws IOException { synchronized (frameWriter) { int lastGoodStreamId; synchronized (this) { if (shutdown) { return; } shutdown = true; lastGoodStreamId = this.lastGoodStreamId; } // TODO: propagate exception message into debugData frameWriter.goAway(lastGoodStreamId, statusCode, Util.EMPTY_BYTE_ARRAY); } }
/** * Sends a connection header if the current variant requires it. This should be called after * {@link Builder#build} for all new connections. */ public void sendConnectionHeader() throws IOException { frameWriter.connectionHeader(); frameWriter.settings(okHttpSettings); }
public void flush() throws IOException { frameWriter.flush(); }
void writeSynReset(int streamId, ErrorCode statusCode) throws IOException { frameWriter.rstStream(streamId, statusCode); }
void writeSynReply(int streamId, boolean outFinished, List<Header> alternating) throws IOException { frameWriter.synReply(outFinished, streamId, alternating); }