コード例 #1
1
    void receive(InputStream in, int byteCount) throws IOException {
      assert (!Thread.holdsLock(SpdyStream.this));

      if (byteCount == 0) {
        return;
      }

      int pos;
      int limit;
      int firstNewByte;
      boolean finished;
      boolean flowControlError;
      synchronized (SpdyStream.this) {
        finished = this.finished;
        pos = this.pos;
        firstNewByte = this.limit;
        limit = this.limit;
        flowControlError = byteCount > buffer.length - available();
      }

      // If the peer sends more data than we can handle, discard it and close the connection.
      if (flowControlError) {
        Util.skipByReading(in, byteCount);
        closeLater(ErrorCode.FLOW_CONTROL_ERROR);
        return;
      }

      // Discard data received after the stream is finished. It's probably a benign race.
      if (finished) {
        Util.skipByReading(in, byteCount);
        return;
      }

      // Fill the buffer without holding any locks. First fill [limit..buffer.length) if that
      // won't overwrite unread data. Then fill [limit..pos). We can't hold a lock, otherwise
      // writes will be blocked until reads complete.
      if (pos < limit) {
        int firstCopyCount = Math.min(byteCount, buffer.length - limit);
        Util.readFully(in, buffer, limit, firstCopyCount);
        limit += firstCopyCount;
        byteCount -= firstCopyCount;
        if (limit == buffer.length) {
          limit = 0;
        }
      }
      if (byteCount > 0) {
        Util.readFully(in, buffer, limit, byteCount);
        limit += byteCount;
      }

      synchronized (SpdyStream.this) {
        // Update the new limit, and mark the position as readable if necessary.
        this.limit = limit;
        if (this.pos == -1) {
          this.pos = firstNewByte;
          SpdyStream.this.notifyAll();
        }
      }
    }
コード例 #2
0
ファイル: HttpEngine.java プロジェクト: 0x4139/GameGuide
  /**
   * Initialize the source for this response. It may be corrected later if the request headers
   * forbids network use.
   */
  private void initResponseSource() throws IOException {
    responseSource = ResponseSource.NETWORK;
    if (!policy.getUseCaches()) return;

    OkResponseCache responseCache = client.getOkResponseCache();
    if (responseCache == null) return;

    CacheResponse candidate =
        responseCache.get(uri, method, requestHeaders.getHeaders().toMultimap(false));
    if (candidate == null) return;

    Map<String, List<String>> responseHeadersMap = candidate.getHeaders();
    cachedResponseBody = candidate.getBody();
    if (!acceptCacheResponseType(candidate)
        || responseHeadersMap == null
        || cachedResponseBody == null) {
      Util.closeQuietly(cachedResponseBody);
      return;
    }

    RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap, true);
    cachedResponseHeaders = new ResponseHeaders(uri, rawResponseHeaders);
    long now = System.currentTimeMillis();
    this.responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders);
    if (responseSource == ResponseSource.CACHE) {
      this.cacheResponse = candidate;
      setResponse(cachedResponseHeaders, cachedResponseBody);
    } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
      this.cacheResponse = candidate;
    } else if (responseSource == ResponseSource.NETWORK) {
      Util.closeQuietly(cachedResponseBody);
    } else {
      throw new AssertionError();
    }
  }
コード例 #3
0
ファイル: OkHttpClient.java プロジェクト: hyongbai/okhttp
 /**
  * Configure the protocols used by this client to communicate with remote servers. By default this
  * client will prefer the most efficient transport available, falling back to more ubiquitous
  * protocols. Applications should only call this method to avoid specific compatibility problems,
  * such as web servers that behave incorrectly when SPDY is enabled.
  *
  * <p>The following protocols are currently supported:
  *
  * <ul>
  *   <li><a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">http/1.1</a>
  *   <li><a href="http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1">spdy/3.1</a>
  *   <li><a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-10">h2-10</a>
  * </ul>
  *
  * <p><strong>This is an evolving set.</strong> Future releases may drop support for transitional
  * protocols (like h2-10), in favor of their successors (h2). The http/1.1 transport will never be
  * dropped.
  *
  * <p>If multiple protocols are specified, <a
  * href="https://technotes.googlecode.com/git/nextprotoneg.html">NPN</a> or <a
  * href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg">ALPN</a> will be used to
  * negotiate a transport.
  *
  * @param protocols the protocols to use, in order of preference. The list must contain {@link
  *     Protocol#HTTP_1_1}. It must not contain null.
  */
 public OkHttpClient setProtocols(List<Protocol> protocols) {
   protocols = Util.immutableList(protocols);
   if (!protocols.contains(Protocol.HTTP_1_1)) {
     throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols);
   }
   if (protocols.contains(null)) {
     throw new IllegalArgumentException("protocols must not contain null");
   }
   this.protocols = Util.immutableList(protocols);
   return this;
 }
 {
     if (mediatype == null)
     {
         throw new NullPointerException("type == null");
     } else
     {
         boundary = bytestring;
         contentType = MediaType.parse((new StringBuilder()).append(mediatype).append("; boundary=").append(bytestring.utf8()).toString());
         partHeaders = Util.immutableList(list);
         partBodies = Util.immutableList(list1);
         return;
     }
 }
コード例 #5
0
ファイル: HttpEngine.java プロジェクト: 0x4139/GameGuide
  /**
   * Figures out what the response source will be, and opens a socket to that source if necessary.
   * Prepares the request headers and gets ready to start writing the request body if it exists.
   */
  public final void sendRequest() throws IOException {
    if (responseSource != null) {
      return;
    }

    prepareRawRequestHeaders();
    initResponseSource();
    OkResponseCache responseCache = client.getOkResponseCache();
    if (responseCache != null) {
      responseCache.trackResponse(responseSource);
    }

    // The raw response source may require the network, but the request
    // headers may forbid network use. In that case, dispose of the network
    // response and use a GATEWAY_TIMEOUT response instead, as specified
    // by http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4.
    if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) {
      if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
        Util.closeQuietly(cachedResponseBody);
      }
      this.responseSource = ResponseSource.CACHE;
      this.cacheResponse = GATEWAY_TIMEOUT_RESPONSE;
      RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders(), true);
      setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody());
    }

    if (responseSource.requiresConnection()) {
      sendSocketRequest();
    } else if (connection != null) {
      client.getConnectionPool().recycle(connection);
      connection = null;
    }
  }
コード例 #6
0
ファイル: SampleServer.java プロジェクト: lzkkk/httplite
  private static SSLContext sslContext(String keystoreFile, String password)
      throws GeneralSecurityException, IOException {
    KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
    InputStream in = new FileInputStream(keystoreFile);
    try {
      keystore.load(in, password.toCharArray());
    } finally {
      Util.closeQuietly(in);
    }
    KeyManagerFactory keyManagerFactory =
        KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyManagerFactory.init(keystore, password.toCharArray());

    TrustManagerFactory trustManagerFactory =
        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(keystore);

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(
        keyManagerFactory.getKeyManagers(),
        trustManagerFactory.getTrustManagers(),
        new SecureRandom());

    return sslContext;
  }
コード例 #7
0
ファイル: OkHttpClient.java プロジェクト: hyongbai/okhttp
 /**
  * Returns a shallow copy of this OkHttpClient that uses the system-wide default for each field
  * that hasn't been explicitly configured.
  */
 OkHttpClient copyWithDefaults() {
   OkHttpClient result = clone();
   if (result.proxySelector == null) {
     result.proxySelector = ProxySelector.getDefault();
   }
   if (result.cookieHandler == null) {
     result.cookieHandler = CookieHandler.getDefault();
   }
   if (result.cache == null && result.cacheAdapter == null) {
     // TODO: drop support for the default response cache.
     ResponseCache defaultCache = ResponseCache.getDefault();
     result.cacheAdapter = defaultCache != null ? new CacheAdapter(defaultCache) : null;
   }
   if (result.socketFactory == null) {
     result.socketFactory = SocketFactory.getDefault();
   }
   if (result.sslSocketFactory == null) {
     result.sslSocketFactory = getDefaultSSLSocketFactory();
   }
   if (result.hostnameVerifier == null) {
     result.hostnameVerifier = OkHostnameVerifier.INSTANCE;
   }
   if (result.authenticator == null) {
     result.authenticator = AuthenticatorAdapter.INSTANCE;
   }
   if (result.connectionPool == null) {
     result.connectionPool = ConnectionPool.getDefault();
   }
   if (result.protocols == null) {
     result.protocols = Util.immutableList(Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);
   }
   return result;
 }
コード例 #8
0
ファイル: HttpEngine.java プロジェクト: gdkjrygh/CompSecurity
 private Connection createNextConnection()
 {
     Object obj = client.getConnectionPool();
     do
     {
         Connection connection1 = ((ConnectionPool) (obj)).get(address);
         if (connection1 == null)
         {
             break;
         }
         if (networkRequest.method().equals("GET") || Internal.instance.isReadable(connection1))
         {
             return connection1;
         }
         Util.closeQuietly(connection1.getSocket());
     } while (true);
     try
     {
         obj = new Connection(((ConnectionPool) (obj)), routeSelector.next());
     }
     catch (IOException ioexception)
     {
         throw new RouteException(ioexception);
     }
     return ((Connection) (obj));
 }
コード例 #9
0
ファイル: HttpEngine.java プロジェクト: gdkjrygh/CompSecurity
 private Request networkRequest(Request request)
 {
     com.squareup.okhttp.Request.Builder builder = request.newBuilder();
     if (request.header("Host") == null)
     {
         builder.header("Host", Util.hostHeader(request.httpUrl()));
     }
     if ((connection == null || connection.getProtocol() != Protocol.HTTP_1_0) && request.header("Connection") == null)
     {
         builder.header("Connection", "Keep-Alive");
     }
     if (request.header("Accept-Encoding") == null)
     {
         transparentGzip = true;
         builder.header("Accept-Encoding", "gzip");
     }
     CookieHandler cookiehandler = client.getCookieHandler();
     if (cookiehandler != null)
     {
         java.util.Map map = OkHeaders.toMultimap(builder.build().headers(), null);
         OkHeaders.addCookies(builder, cookiehandler.get(request.uri(), map));
     }
     if (request.header("User-Agent") == null)
     {
         builder.header("User-Agent", Version.userAgent());
     }
     return builder.build();
 }
コード例 #10
0
ファイル: RawHeaders.java プロジェクト: hongxixi/okhttp
 /** Reads headers or trailers into {@code out}. */
 public Builder readHeaders(InputStream in) throws IOException {
   // parse the result headers until the first blank line
   for (String line; (line = Util.readAsciiLine(in)).length() != 0; ) {
     addLine(line);
   }
   return this;
 }
コード例 #11
0
ファイル: RawHeaders.java プロジェクト: hongxixi/okhttp
 private RawHeaders(Builder builder) {
   this.namesAndValues = Util.immutableList(builder.namesAndValues);
   this.requestLine = builder.requestLine;
   this.statusLine = builder.statusLine;
   this.httpMinorVersion = builder.httpMinorVersion;
   this.responseCode = builder.responseCode;
   this.responseMessage = builder.responseMessage;
 }
コード例 #12
0
ファイル: Dispatcher.java プロジェクト: BinSlashBash/xcrumby
 public synchronized void cancel(Object tag) {
   Iterator<AsyncCall> i = this.readyCalls.iterator();
   while (i.hasNext()) {
     if (Util.equal(tag, ((AsyncCall) i.next()).tag())) {
       i.remove();
     }
   }
   for (AsyncCall call : this.runningCalls) {
     if (Util.equal(tag, call.tag())) {
       call.get().canceled = true;
       HttpEngine engine = call.get().engine;
       if (engine != null) {
         engine.disconnect();
       }
     }
   }
 }
コード例 #13
0
ファイル: HttpEngine.java プロジェクト: gdkjrygh/CompSecurity
 public void close()
 {
     if (!cacheRequestClosed && !Util.discard(this, 100, TimeUnit.MILLISECONDS))
     {
         cacheRequestClosed = true;
         cacheRequest.abort();
     }
     source.close();
 }
コード例 #14
0
ファイル: ConnectionSpec.java プロジェクト: luohaohaha/okhttp
  /**
   * Returns the cipher suites to use for a connection. Returns {@code null} if all of the SSL
   * socket's enabled cipher suites should be used.
   */
  public List<CipherSuite> cipherSuites() {
    if (cipherSuites == null) return null;

    CipherSuite[] result = new CipherSuite[cipherSuites.length];
    for (int i = 0; i < cipherSuites.length; i++) {
      result[i] = CipherSuite.forJavaName(cipherSuites[i]);
    }
    return Util.immutableList(result);
  }
コード例 #15
0
ファイル: ConnectionSpec.java プロジェクト: luohaohaha/okhttp
  /**
   * Returns the TLS versions to use when negotiating a connection. Returns {@code null} if all of
   * the SSL socket's enabled TLS versions should be used.
   */
  public List<TlsVersion> tlsVersions() {
    if (tlsVersions == null) return null;

    TlsVersion[] result = new TlsVersion[tlsVersions.length];
    for (int i = 0; i < tlsVersions.length; i++) {
      result[i] = TlsVersion.forJavaName(tlsVersions[i]);
    }
    return Util.immutableList(result);
  }
コード例 #16
0
ファイル: HttpEngine.java プロジェクト: 0x4139/GameGuide
  /**
   * Releases this engine so that its resources may be either reused or closed. Also call {@link
   * #automaticallyReleaseConnectionToPool} unless the connection will be used to follow a redirect.
   */
  public final void release(boolean streamCanceled) {
    // If the response body comes from the cache, close it.
    if (responseBodyIn == cachedResponseBody) {
      Util.closeQuietly(responseBodyIn);
    }

    if (!connectionReleased && connection != null) {
      connectionReleased = true;

      if (transport == null
          || !transport.makeReusable(streamCanceled, requestBodyOut, responseTransferIn)) {
        Util.closeQuietly(connection);
        connection = null;
      } else if (automaticallyReleaseConnectionToPool) {
        client.getConnectionPool().recycle(connection);
        connection = null;
      }
    }
  }
コード例 #17
0
ファイル: RawHeaders.java プロジェクト: hongxixi/okhttp
 /** Parses bytes of a response header from an HTTP transport. */
 public static RawHeaders readHttpHeaders(InputStream in) throws IOException {
   Builder builder;
   do {
     builder = new Builder();
     builder.set(ResponseHeaders.SELECTED_TRANSPORT, "http/1.1");
     builder.setStatusLine(Util.readAsciiLine(in));
     builder.readHeaders(in);
   } while (builder.responseCode == HttpEngine.HTTP_CONTINUE);
   return builder.build();
 }
コード例 #18
0
 @Override
 public final Permission getPermission() throws IOException {
   String hostName = getURL().getHost();
   int hostPort = Util.getEffectivePort(getURL());
   if (usingProxy()) {
     InetSocketAddress proxyAddress = (InetSocketAddress) requestedProxy.address();
     hostName = proxyAddress.getHostName();
     hostPort = proxyAddress.getPort();
   }
   return new SocketPermission(hostName + ":" + hostPort, "connect, resolve");
 }
コード例 #19
0
ファイル: ConnectionSpec.java プロジェクト: luohaohaha/okhttp
  /**
   * Returns a copy of this that omits cipher suites and TLS versions not enabled by {@code
   * sslSocket}.
   */
  private ConnectionSpec supportedSpec(SSLSocket sslSocket, boolean isFallback) {
    String[] cipherSuitesIntersection =
        cipherSuites != null
            ? Util.intersect(String.class, cipherSuites, sslSocket.getEnabledCipherSuites())
            : sslSocket.getEnabledCipherSuites();
    String[] tlsVersionsIntersection =
        tlsVersions != null
            ? Util.intersect(String.class, tlsVersions, sslSocket.getEnabledProtocols())
            : sslSocket.getEnabledProtocols();

    // In accordance with https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00
    // the SCSV cipher is added to signal that a protocol fallback has taken place.
    if (isFallback && contains(sslSocket.getSupportedCipherSuites(), "TLS_FALLBACK_SCSV")) {
      cipherSuitesIntersection = concat(cipherSuitesIntersection, "TLS_FALLBACK_SCSV");
    }

    return new Builder(this)
        .cipherSuites(cipherSuitesIntersection)
        .tlsVersions(tlsVersionsIntersection)
        .build();
  }
コード例 #20
0
ファイル: Dispatcher.java プロジェクト: BinSlashBash/xcrumby
 public synchronized ExecutorService getExecutorService() {
   if (this.executorService == null) {
     this.executorService =
         new ThreadPoolExecutor(
             0,
             Integer.MAX_VALUE,
             60,
             TimeUnit.SECONDS,
             new LinkedBlockingQueue(),
             Util.threadFactory("OkHttp Dispatcher", false));
   }
   return this.executorService;
 }
コード例 #21
0
 @Override
 public final void disconnect() {
   // Calling disconnect() before a connection exists should have no effect.
   if (httpEngine != null) {
     // We close the response body here instead of in
     // HttpEngine.release because that is called when input
     // has been completely read from the underlying socket.
     // However the response body can be a GZIPInputStream that
     // still has unread data.
     if (httpEngine.hasResponse()) {
       Util.closeQuietly(httpEngine.getResponseBody());
     }
     httpEngine.release(true);
   }
 }
コード例 #22
0
 public int read(byte abyte0[], int i, int j) throws IOException {
   Util.checkOffsetAndCount(abyte0.length, i, j);
   checkNotClosed();
   if (in == null || inputExhausted) {
     return -1;
   }
   j = in.read(abyte0, i, j);
   if (j == -1) {
     inputExhausted = true;
     endOfInput();
     return -1;
   } else {
     cacheWrite(abyte0, i, j);
     return j;
   }
 }
コード例 #23
0
ファイル: GifLoadTask.java プロジェクト: chimuca/teamtalk
 private FilterInputStream getFromCache(String url) throws Exception {
   DiskLruCache cache = DiskLruCache.open(CommonUtil.getImageSavePath(), 1, 2, 2 * 1024 * 1024);
   cache.flush();
   String key = Util.hash(url);
   final DiskLruCache.Snapshot snapshot;
   try {
     snapshot = cache.get(key);
     if (snapshot == null) {
       return null;
     }
   } catch (IOException e) {
     return null;
   }
   FilterInputStream bodyIn =
       new FilterInputStream(snapshot.getInputStream(1)) {
         @Override
         public void close() throws IOException {
           snapshot.close();
           super.close();
         }
       };
   return bodyIn;
 }
コード例 #24
0
 RawHeaders getRequestHeaders() {
   RawHeaders rawheaders = new RawHeaders();
   rawheaders.setRequestLine(
       (new StringBuilder())
           .append("CONNECT ")
           .append(host)
           .append(":")
           .append(port)
           .append(" HTTP/1.1")
           .toString());
   String s;
   if (port == Util.getDefaultPort("https")) {
     s = host;
   } else {
     s = (new StringBuilder()).append(host).append(":").append(port).toString();
   }
   rawheaders.set("Host", s);
   rawheaders.set("User-Agent", userAgent);
   if (proxyAuthorization != null) {
     rawheaders.set("Proxy-Authorization", proxyAuthorization);
   }
   rawheaders.set("Proxy-Connection", "Keep-Alive");
   return rawheaders;
 }
コード例 #25
0
ファイル: CertificatePinner.java プロジェクト: sheytoon/Salam
 private CertificatePinner(Builder builder) {
   this.hostnameToPins = Util.immutableMap(builder.hostnameToPins);
 }
コード例 #26
0
ファイル: HttpEngine.java プロジェクト: 0x4139/GameGuide
  /**
   * Flushes the remaining request header and body, parses the HTTP response headers and starts
   * reading the HTTP response body if it exists.
   */
  public final void readResponse() throws IOException {
    if (hasResponse()) {
      responseHeaders.setResponseSource(responseSource);
      return;
    }

    if (responseSource == null) {
      throw new IllegalStateException("readResponse() without sendRequest()");
    }

    if (!responseSource.requiresConnection()) {
      return;
    }

    if (sentRequestMillis == -1) {
      if (requestBodyOut instanceof RetryableOutputStream) {
        int contentLength = ((RetryableOutputStream) requestBodyOut).contentLength();
        requestHeaders.setContentLength(contentLength);
      }
      transport.writeRequestHeaders();
    }

    if (requestBodyOut != null) {
      requestBodyOut.close();
      if (requestBodyOut instanceof RetryableOutputStream) {
        transport.writeRequestBody((RetryableOutputStream) requestBodyOut);
      }
    }

    transport.flushRequest();

    responseHeaders = transport.readResponseHeaders();
    responseHeaders.setLocalTimestamps(sentRequestMillis, System.currentTimeMillis());
    responseHeaders.setResponseSource(responseSource);

    if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
      if (cachedResponseHeaders.validate(responseHeaders)) {
        release(false);
        ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders);
        this.responseHeaders = combinedHeaders;

        // Update the cache after applying the combined headers but before initializing the content
        // stream, otherwise the Content-Encoding header (if present) will be stripped from the
        // combined headers and not end up in the cache file if transparent gzip compression is
        // turned on.
        OkResponseCache responseCache = client.getOkResponseCache();
        responseCache.trackConditionalCacheHit();
        responseCache.update(cacheResponse, policy.getHttpConnectionToCache());

        initContentStream(cachedResponseBody);
        return;
      } else {
        Util.closeQuietly(cachedResponseBody);
      }
    }

    if (hasResponseBody()) {
      maybeCache(); // reentrant. this calls into user code which may call back into this!
    }

    initContentStream(transport.getTransferStream(cacheRequest));
  }
コード例 #27
0
 public boolean equals(Object paramObject) {
   return ((paramObject instanceof Challenge))
       && (Util.equal(this.scheme, ((Challenge) paramObject).scheme))
       && (Util.equal(this.realm, ((Challenge) paramObject).realm));
 }
コード例 #28
0
/**
 * A socket connection to a remote peer. A connection hosts streams which can send and receive data.
 *
 * <p>Many methods in this API are <strong>synchronous:</strong> the call is completed before the
 * method returns. This is typical for Java but atypical for SPDY. This is motivated by exception
 * transparency: an IOException that was triggered by a certain caller can be caught and handled by
 * that caller.
 */
public final class SpdyConnection implements Closeable {

  // Internal state of this connection is guarded by 'this'. No blocking
  // operations may be performed while holding this lock!
  //
  // Socket writes are guarded by frameWriter.
  //
  // Socket reads are unguarded but are only made by the reader thread.
  //
  // Certain operations (like SYN_STREAM) need to synchronize on both the
  // frameWriter (to do blocking I/O) and this (to create streams). Such
  // operations must synchronize on 'this' last. This ensures that we never
  // wait for a blocking operation while holding 'this'.

  private static final ExecutorService executor =
      new ThreadPoolExecutor(
          0,
          Integer.MAX_VALUE,
          60,
          TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(),
          Util.threadFactory("OkHttp SpdyConnection", true));

  /** The protocol variant, like {@link com.squareup.okhttp.internal.spdy.Spdy3}. */
  final Protocol protocol;

  /** True if this peer initiated the connection. */
  final boolean client;

  /**
   * User code to run in response to an incoming stream. Callbacks must not be run on the callback
   * executor.
   */
  private final IncomingStreamHandler handler;

  private final Map<Integer, SpdyStream> streams = new HashMap<Integer, SpdyStream>();
  private final String hostName;
  private int lastGoodStreamId;
  private int nextStreamId;
  private boolean shutdown;
  private long idleStartTimeNs = System.nanoTime();

  /** Lazily-created map of in-flight pings awaiting a response. Guarded by this. */
  private Map<Integer, Ping> pings;
  /** User code to run in response to push promise events. */
  private final PushObserver pushObserver;

  private int nextPingId;

  /**
   * The total number of bytes consumed by the application, but not yet acknowledged by sending a
   * {@code WINDOW_UPDATE} frame on this connection.
   */
  // Visible for testing
  long unacknowledgedBytesRead = 0;

  /** Count of bytes that can be written on the connection before receiving a window update. */
  // Visible for testing
  long bytesLeftInWriteWindow;

  /** Settings we communicate to the peer. */
  // TODO: Do we want to dynamically adjust settings, or KISS and only set once?
  final Settings okHttpSettings = new Settings();
  // okHttpSettings.set(Settings.MAX_CONCURRENT_STREAMS, 0, max);

  /** Settings we receive from the peer. */
  // TODO: MWS will need to guard on this setting before attempting to push.
  final Settings peerSettings = new Settings();

  private boolean receivedInitialPeerSettings = false;
  final FrameReader frameReader;
  final FrameWriter frameWriter;
  final long maxFrameSize;

  // Visible for testing
  final Reader readerRunnable;

  private SpdyConnection(Builder builder) {
    protocol = builder.protocol;
    pushObserver = builder.pushObserver;
    client = builder.client;
    handler = builder.handler;
    nextStreamId = builder.client ? 1 : 2;
    nextPingId = builder.client ? 1 : 2;

    // Flow control was designed more for servers, or proxies than edge clients.
    // If we are a client, set the flow control window to 16MiB.  This avoids
    // thrashing window updates every 64KiB, yet small enough to avoid blowing
    // up the heap.
    if (builder.client) {
      okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, 16 * 1024 * 1024);
    }

    hostName = builder.hostName;

    Variant variant;
    if (protocol == Protocol.HTTP_2) {
      variant = new Http20Draft09();
    } else if (protocol == Protocol.SPDY_3) {
      variant = new Spdy3();
    } else {
      throw new AssertionError(protocol);
    }
    bytesLeftInWriteWindow = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
    frameReader = variant.newReader(builder.source, client);
    frameWriter = variant.newWriter(builder.sink, client);
    maxFrameSize = variant.maxFrameSize();

    readerRunnable = new Reader();
    new Thread(readerRunnable).start(); // Not a daemon thread.
  }

  /** The protocol as selected using NPN or ALPN. */
  public Protocol getProtocol() {
    return protocol;
  }

  /** Returns the number of {@link SpdyStream#isOpen() open streams} on this connection. */
  public synchronized int openStreamCount() {
    return streams.size();
  }

  synchronized SpdyStream getStream(int id) {
    return streams.get(id);
  }

  synchronized SpdyStream removeStream(int streamId) {
    SpdyStream stream = streams.remove(streamId);
    if (stream != null && streams.isEmpty()) {
      setIdle(true);
    }
    return stream;
  }

  private synchronized void setIdle(boolean value) {
    idleStartTimeNs = value ? System.nanoTime() : Long.MAX_VALUE;
  }

  /** Returns true if this connection is idle. */
  public synchronized boolean isIdle() {
    return idleStartTimeNs != Long.MAX_VALUE;
  }

  /**
   * Returns the time in ns when this connection became idle or Long.MAX_VALUE if connection is not
   * idle.
   */
  public synchronized long getIdleStartTimeNs() {
    return idleStartTimeNs;
  }

  /**
   * Returns a new server-initiated stream.
   *
   * @param associatedStreamId the stream that triggered the sender to create this 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 SpdyStream pushStream(int associatedStreamId, List<Header> requestHeaders, boolean out)
      throws IOException {
    if (client) throw new IllegalStateException("Client cannot push requests.");
    if (protocol != Protocol.HTTP_2) throw new IllegalStateException("protocol != HTTP_2");
    return newStream(associatedStreamId, requestHeaders, out, false);
  }

  /**
   * Returns a new locally-initiated 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}.
   * @param in true to create an input stream that the remote peer can use to send data to us.
   *     Corresponds to {@code FLAG_UNIDIRECTIONAL}.
   */
  public SpdyStream newStream(List<Header> requestHeaders, boolean out, boolean in)
      throws IOException {
    return newStream(0, requestHeaders, out, in);
  }

  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;
  }

  void writeSynReply(int streamId, boolean outFinished, List<Header> alternating)
      throws IOException {
    frameWriter.synReply(outFinished, streamId, alternating);
  }

  /**
   * 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);
    }
  }

  /** {@code delta} will be negative if a settings frame initial window is smaller than the last. */
  void addBytesToWriteWindow(long delta) {
    bytesLeftInWriteWindow += delta;
    if (delta > 0) SpdyConnection.this.notifyAll();
  }

  void writeSynResetLater(final int streamId, final ErrorCode errorCode) {
    executor.submit(
        new NamedRunnable("OkHttp %s stream %d", hostName, streamId) {
          @Override
          public void execute() {
            try {
              writeSynReset(streamId, errorCode);
            } catch (IOException ignored) {
            }
          }
        });
  }

  void writeSynReset(int streamId, ErrorCode statusCode) throws IOException {
    frameWriter.rstStream(streamId, statusCode);
  }

  void writeWindowUpdateLater(final int streamId, final long unacknowledgedBytesRead) {
    executor.submit(
        new NamedRunnable("OkHttp Window Update %s stream %d", hostName, streamId) {
          @Override
          public void execute() {
            try {
              frameWriter.windowUpdate(streamId, unacknowledgedBytesRead);
            } catch (IOException ignored) {
            }
          }
        });
  }

  /**
   * Sends a ping frame to the peer. Use the returned object to await the ping's response and
   * observe its round trip time.
   */
  public Ping ping() throws IOException {
    Ping ping = new Ping();
    int pingId;
    synchronized (this) {
      if (shutdown) {
        throw new IOException("shutdown");
      }
      pingId = nextPingId;
      nextPingId += 2;
      if (pings == null) pings = new HashMap<Integer, Ping>();
      pings.put(pingId, ping);
    }
    writePing(false, pingId, 0x4f4b6f6b /* ASCII "OKok" */, ping);
    return ping;
  }

  private void writePingLater(
      final boolean reply, final int payload1, final int payload2, final Ping ping) {
    executor.submit(
        new NamedRunnable("OkHttp %s ping %08x%08x", hostName, payload1, payload2) {
          @Override
          public void execute() {
            try {
              writePing(reply, payload1, payload2, ping);
            } catch (IOException ignored) {
            }
          }
        });
  }

  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);
    }
  }

  private synchronized Ping removePing(int id) {
    return pings != null ? pings.remove(id) : null;
  }

  public void flush() throws IOException {
    frameWriter.flush();
  }

  /**
   * 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);
    }
  }

  /**
   * Closes this connection. This cancels all open streams and unanswered pings. It closes the
   * underlying input and output streams and shuts down internal executor services.
   */
  @Override
  public void close() throws IOException {
    close(ErrorCode.NO_ERROR, ErrorCode.CANCEL);
  }

  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;
  }

  /**
   * 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 static class Builder {
    private String hostName;
    private BufferedSource source;
    private BufferedSink sink;
    private IncomingStreamHandler handler = IncomingStreamHandler.REFUSE_INCOMING_STREAMS;
    private Protocol protocol = Protocol.SPDY_3;
    private PushObserver pushObserver = PushObserver.CANCEL;
    private boolean client;

    public Builder(boolean client, Socket socket) throws IOException {
      this(
          "",
          client,
          Okio.buffer(Okio.source(socket.getInputStream())),
          Okio.buffer(Okio.sink(socket.getOutputStream())));
    }

    /**
     * @param client true if this peer initiated the connection; false if this peer accepted the
     *     connection.
     */
    public Builder(String hostName, boolean client, BufferedSource source, BufferedSink sink) {
      this.hostName = hostName;
      this.client = client;
      this.source = source;
      this.sink = sink;
    }

    public Builder handler(IncomingStreamHandler handler) {
      this.handler = handler;
      return this;
    }

    public Builder protocol(Protocol protocol) {
      this.protocol = protocol;
      return this;
    }

    public Builder pushObserver(PushObserver pushObserver) {
      this.pushObserver = pushObserver;
      return this;
    }

    public SpdyConnection build() {
      return new SpdyConnection(this);
    }
  }

  /**
   * Methods in this class must not lock FrameWriter. If a method needs to write a frame, create an
   * async task to do so.
   */
  class Reader extends NamedRunnable implements FrameReader.Handler {
    private Reader() {
      super("OkHttp %s", hostName);
    }

    @Override
    protected void execute() {
      ErrorCode connectionErrorCode = ErrorCode.INTERNAL_ERROR;
      ErrorCode streamErrorCode = ErrorCode.INTERNAL_ERROR;
      try {
        if (!client) {
          frameReader.readConnectionHeader();
        }
        while (frameReader.nextFrame(this)) {}
        connectionErrorCode = ErrorCode.NO_ERROR;
        streamErrorCode = ErrorCode.CANCEL;
      } catch (IOException e) {
        connectionErrorCode = ErrorCode.PROTOCOL_ERROR;
        streamErrorCode = ErrorCode.PROTOCOL_ERROR;
      } finally {
        try {
          close(connectionErrorCode, streamErrorCode);
        } catch (IOException ignored) {
        }
      }
    }

    @Override
    public void data(boolean inFinished, int streamId, BufferedSource source, int length)
        throws IOException {
      if (pushedStream(streamId)) {
        pushDataLater(streamId, source, length, inFinished);
        return;
      }
      SpdyStream dataStream = getStream(streamId);
      if (dataStream == null) {
        writeSynResetLater(streamId, ErrorCode.INVALID_STREAM);
        source.skip(length);
        return;
      }
      dataStream.receiveData(source, length);
      if (inFinished) {
        dataStream.receiveFin();
      }
    }

    @Override
    public void headers(
        boolean outFinished,
        boolean inFinished,
        int streamId,
        int associatedStreamId,
        int priority,
        List<Header> headerBlock,
        HeadersMode headersMode) {
      if (pushedStream(streamId)) {
        pushHeadersLater(streamId, headerBlock, inFinished);
        return;
      }
      SpdyStream stream;
      synchronized (SpdyConnection.this) {
        // If we're shutdown, don't bother with this stream.
        if (shutdown) return;

        stream = getStream(streamId);

        if (stream == null) {
          // The headers claim to be for an existing stream, but we don't have one.
          if (headersMode.failIfStreamAbsent()) {
            writeSynResetLater(streamId, ErrorCode.INVALID_STREAM);
            return;
          }

          // If the stream ID is less than the last created ID, assume it's already closed.
          if (streamId <= lastGoodStreamId) return;

          // If the stream ID is in the client's namespace, assume it's already closed.
          if (streamId % 2 == nextStreamId % 2) return;

          // Create a stream.
          final SpdyStream newStream =
              new SpdyStream(
                  streamId, SpdyConnection.this, outFinished, inFinished, priority, headerBlock);
          lastGoodStreamId = streamId;
          streams.put(streamId, newStream);
          executor.submit(
              new NamedRunnable("OkHttp %s stream %d", hostName, streamId) {
                @Override
                public void execute() {
                  try {
                    handler.receive(newStream);
                  } catch (IOException e) {
                    throw new RuntimeException(e);
                  }
                }
              });
          return;
        }
      }

      // The headers claim to be for a new stream, but we already have one.
      if (headersMode.failIfStreamPresent()) {
        stream.closeLater(ErrorCode.PROTOCOL_ERROR);
        removeStream(streamId);
        return;
      }

      // Update an existing stream.
      stream.receiveHeaders(headerBlock, headersMode);
      if (inFinished) stream.receiveFin();
    }

    @Override
    public void rstStream(int streamId, ErrorCode errorCode) {
      if (pushedStream(streamId)) {
        pushResetLater(streamId, errorCode);
        return;
      }
      SpdyStream rstStream = removeStream(streamId);
      if (rstStream != null) {
        rstStream.receiveRstStream(errorCode);
      }
    }

    @Override
    public void settings(boolean clearPrevious, Settings newSettings) {
      long delta = 0;
      SpdyStream[] streamsToNotify = null;
      synchronized (SpdyConnection.this) {
        int priorWriteWindowSize = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
        if (clearPrevious) peerSettings.clear();
        peerSettings.merge(newSettings);
        if (getProtocol() == Protocol.HTTP_2) {
          ackSettingsLater();
        }
        int peerInitialWindowSize = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
        if (peerInitialWindowSize != -1 && peerInitialWindowSize != priorWriteWindowSize) {
          delta = peerInitialWindowSize - priorWriteWindowSize;
          if (!receivedInitialPeerSettings) {
            addBytesToWriteWindow(delta);
            receivedInitialPeerSettings = true;
          }
          if (!streams.isEmpty()) {
            streamsToNotify = streams.values().toArray(new SpdyStream[streams.size()]);
          }
        }
      }
      if (streamsToNotify != null && delta != 0) {
        for (SpdyStream stream : streams.values()) {
          synchronized (stream) {
            stream.addBytesToWriteWindow(delta);
          }
        }
      }
    }

    private void ackSettingsLater() {
      executor.submit(
          new NamedRunnable("OkHttp %s ACK Settings", hostName) {
            @Override
            public void execute() {
              try {
                frameWriter.ackSettings();
              } catch (IOException ignored) {
              }
            }
          });
    }

    @Override
    public void ackSettings() {
      // TODO: If we don't get this callback after sending settings to the peer, SETTINGS_TIMEOUT.
    }

    @Override
    public void ping(boolean reply, int payload1, int payload2) {
      if (reply) {
        Ping ping = removePing(payload1);
        if (ping != null) {
          ping.receive();
        }
      } else {
        // Send a reply to a client ping if this is a server and vice versa.
        writePingLater(true, payload1, payload2, null);
      }
    }

    @Override
    public void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
      if (debugData.size() > 0) { // TODO: log the debugData
      }
      synchronized (SpdyConnection.this) {
        shutdown = true;

        // Fail all streams created after the last good stream ID.
        for (Iterator<Map.Entry<Integer, SpdyStream>> i = streams.entrySet().iterator();
            i.hasNext(); ) {
          Map.Entry<Integer, SpdyStream> entry = i.next();
          int streamId = entry.getKey();
          if (streamId > lastGoodStreamId && entry.getValue().isLocallyInitiated()) {
            entry.getValue().receiveRstStream(ErrorCode.REFUSED_STREAM);
            i.remove();
          }
        }
      }
    }

    @Override
    public void windowUpdate(int streamId, long windowSizeIncrement) {
      if (streamId == 0) {
        synchronized (SpdyConnection.this) {
          bytesLeftInWriteWindow += windowSizeIncrement;
          SpdyConnection.this.notifyAll();
        }
      } else {
        SpdyStream stream = getStream(streamId);
        if (stream != null) {
          synchronized (stream) {
            stream.addBytesToWriteWindow(windowSizeIncrement);
          }
        }
      }
    }

    @Override
    public void priority(int streamId, int priority) {
      // TODO: honor priority.
    }

    @Override
    public void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders) {
      pushRequestLater(promisedStreamId, requestHeaders);
    }
  }

  /** Even, positive numbered streams are pushed streams in HTTP/2. */
  private boolean pushedStream(int streamId) {
    return protocol == Protocol.HTTP_2 && streamId != 0 && (streamId & 1) == 0;
  }

  // Guarded by this.
  private final Set<Integer> currentPushRequests = new LinkedHashSet<Integer>();

  private void pushRequestLater(final int streamId, final List<Header> requestHeaders) {
    synchronized (this) {
      if (currentPushRequests.contains(streamId)) {
        writeSynResetLater(streamId, ErrorCode.PROTOCOL_ERROR);
        return;
      }
      currentPushRequests.add(streamId);
    }
    executor.submit(
        new NamedRunnable("OkHttp %s Push Request[%s]", hostName, streamId) {
          @Override
          public void execute() {
            boolean cancel = pushObserver.onRequest(streamId, requestHeaders);
            try {
              if (cancel) {
                frameWriter.rstStream(streamId, ErrorCode.CANCEL);
                synchronized (SpdyConnection.this) {
                  currentPushRequests.remove(streamId);
                }
              }
            } catch (IOException ignored) {
            }
          }
        });
  }

  private void pushHeadersLater(
      final int streamId, final List<Header> requestHeaders, final boolean inFinished) {
    executor.submit(
        new NamedRunnable("OkHttp %s Push Headers[%s]", hostName, streamId) {
          @Override
          public void execute() {
            boolean cancel = pushObserver.onHeaders(streamId, requestHeaders, inFinished);
            try {
              if (cancel) frameWriter.rstStream(streamId, ErrorCode.CANCEL);
              if (cancel || inFinished) {
                synchronized (SpdyConnection.this) {
                  currentPushRequests.remove(streamId);
                }
              }
            } catch (IOException ignored) {
            }
          }
        });
  }

  /**
   * Eagerly reads {@code byteCount} bytes from the source before launching a background task to
   * process the data. This avoids corrupting the stream.
   */
  private void pushDataLater(
      final int streamId,
      final BufferedSource source,
      final int byteCount,
      final boolean inFinished)
      throws IOException {
    final OkBuffer buffer = new OkBuffer();
    source.require(byteCount); // Eagerly read the frame before firing client thread.
    source.read(buffer, byteCount);
    if (buffer.size() != byteCount) throw new IOException(buffer.size() + " != " + byteCount);
    executor.submit(
        new NamedRunnable("OkHttp %s Push Data[%s]", hostName, streamId) {
          @Override
          public void execute() {
            try {
              boolean cancel = pushObserver.onData(streamId, buffer, byteCount, inFinished);
              if (cancel) frameWriter.rstStream(streamId, ErrorCode.CANCEL);
              if (cancel || inFinished) {
                synchronized (SpdyConnection.this) {
                  currentPushRequests.remove(streamId);
                }
              }
            } catch (IOException ignored) {
            }
          }
        });
  }

  private void pushResetLater(final int streamId, final ErrorCode errorCode) {
    executor.submit(
        new NamedRunnable("OkHttp %s Push Reset[%s]", hostName, streamId) {
          @Override
          public void execute() {
            pushObserver.onReset(streamId, errorCode);
            synchronized (SpdyConnection.this) {
              currentPushRequests.remove(streamId);
            }
          }
        });
  }
}
コード例 #29
0
 @Override
 public int read() throws IOException {
   return Util.readSingleByte(this);
 }
コード例 #30
0
 @Override
 public void write(int b) throws IOException {
   Util.writeSingleByte(this, b);
 }