Okhttp3(7) - ConnectInterceptor 정보

11084 단어 Okhttp 시작
전편에서 우리는 앞으로 캐시 차단기를 사용할 것이다. 이 시리즈가 Okhttp를 사용하기 때문에 캐시와 관련된 것을 많이 사용하지 않았고, 이후에 또 하나의 시리즈를 열 기회가 있을 것이다.오늘 우리는 밑에서 두 번째 차단기인 연결 차단기를 이야기하자. 이것이야말로 진정으로 서비스 측에 공격을 시작하는 것이다. 동지들은 이미 조급해서 견딜 수 없는 것이 아니냐. 하하, 곧 올 것이다.
원본 코드
  • 최초 실례화된 채널 대상 획득
  • httpcodec
  • 1개 얻기
  • 연결 대상 1개
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    @Override public Response intercept(Chain chain) throws IOException {
      RealInterceptorChain realChain = (RealInterceptorChain) chain;
      Request request = realChain.request();
      StreamAllocation streamAllocation = realChain.streamAllocation();
    
      // We need the network to satisfy this request. Possibly for validating a conditional GET.
      boolean doExtensiveHealthChecks = !request.method().equals("GET");
      HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
      RealConnection connection = streamAllocation.connection();
    
      return realChain.proceed(request, streamAllocation, httpCodec, connection);
    }
    

    이곳의 논리는 매우 간단하다. 왜냐하면 이미 다른 유형의 대상에게 전가되었기 때문이다.앞에서 언급한 바와 같이 RealInterceptorChain 구조 함수는 네 가지 중요한 속성(Request,StreamAllocation,HttpCodec,Connection)이 있는데 첫 번째는 말할 것도 없고 두 번째는 차단기에서 실례화된 것이다. 셋째, 넷째는 이 절에서 허리를 실례화한 것이다.
    httpcodec 가져오기
    Stream Allocation의 new Stream () 방법으로 Http Codec를 얻을 수 있습니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
     //  
      try {
        // 1. , http2.0 , 
        RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
            writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
        // 2. httpcodec, http2.0 Http2Codec Http2Codec
        HttpCodec resultCodec = resultConnection.newCodec(client, this);
    
        synchronized (connectionPool) {
          codec = resultCodec;
          return resultCodec;
        }
      } catch (IOException e) {
        throw new RouteException(e);
      }
    }
    
  • 건강 연결에 대한 판단
  • socket이 닫히지 않았습니다
  • 입력 흐름이 닫히지 않았습니다
  • 출력 흐름이 닫히지 않았습니다
  • http2에서 연결이 꺼지지 않았습니다

  • 연결 가져오기 및 연결
    프로세스:
  • 마지막 연결이 정상적인지 여부, 예, 직접 사용
  • 캐시에 사용할 수 있는 것이 있는지 없는지, 있음, 사용
  • 직접 생성
  • 악수, 연결
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    
    private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
        boolean connectionRetryEnabled) throws IOException {
      Route selectedRoute;
      synchronized (connectionPool) {
        //  
        if (released) throw new IllegalStateException("released");
        if (codec != null) throw new IllegalStateException("codec != null");
        if (canceled) throw new IOException("Canceled");
    
        //  
        // Attempt to use an already-allocated connection.
        RealConnection allocatedConnection = this.connection;
        if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
          return allocatedConnection;
        }
    
        //  
        // Attempt to get a connection from the pool.
        Internal.instance.get(connectionPool, address, this);
        if (connection != null) {
          return connection;
        }
    
        selectedRoute = route;
      }
    
      //  , ip 
      // If we need a route, make one. This is a blocking operation.
      if (selectedRoute == null) {
        //  
        selectedRoute = routeSelector.next();
      }
    
      // Create a connection and assign it to this allocation immediately. This makes it possible for
      // an asynchronous cancel() to interrupt the handshake we're about to do.
      //  , 
      RealConnection result;
      synchronized (connectionPool) {
        route = selectedRoute;
        refusedStreamCount = 0;
        result = new RealConnection(connectionPool, selectedRoute);
        acquire(result);
        if (canceled) throw new IOException("Canceled");
      }
    
      //    
      // Do TCP + TLS handshakes. This is a blocking operation.
      result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
      routeDatabase().connected(result.route());
    
      Socket socket = null;
      //  
      synchronized (connectionPool) {
        // Pool the connection.
        Internal.instance.put(connectionPool, result);
    
        // If another multiplexed connection to the same address was created concurrently, then
        // release this connection and acquire that one.
        if (result.isMultiplexed()) {
          socket = Internal.instance.deduplicate(connectionPool, address, this);
          result = connection;
        }
      }
      closeQuietly(socket);
    
      return result;
    }
    

    잇닿다
    연결을 맺는 것이 비교적 중요한 단계다.Https라면 인증서 절차가 있습니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    
    public void connect(
        int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
      if (protocol != null) throw new IllegalStateException("already connected");
    //  
      RouteException routeException = null;
      List connectionSpecs = route.address().connectionSpecs();
      ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
    
      if (route.address().sslSocketFactory() == null) {
        if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
          throw new RouteException(new UnknownServiceException(
              "CLEARTEXT communication not enabled for client"));
        }
        String host = route.address().url().host();
        if (!Platform.get().isCleartextTrafficPermitted(host)) {
          throw new RouteException(new UnknownServiceException(
              "CLEARTEXT communication to " + host + " not permitted by network security policy"));
        }
      }
    
    //  
      while (true) {
        try {
        //  , , 
          if (route.requiresTunnel()) {
            connectTunnel(connectTimeout, readTimeout, writeTimeout);
          } else {
          //  , socket 
            connectSocket(connectTimeout, readTimeout);
          }
          // https 
          establishProtocol(connectionSpecSelector);
          break;
        } catch (IOException e) {
          closeQuietly(socket);
          closeQuietly(rawSocket);
          socket = null;
          rawSocket = null;
          source = null;
          sink = null;
          handshake = null;
          protocol = null;
          http2Connection = null;
    
          if (routeException == null) {
            routeException = new RouteException(e);
          } else {
            routeException.addConnectException(e);
          }
    
          if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
            throw routeException;
          }
        }
      }
    
      if (http2Connection != null) {
        synchronized (connectionPool) {
          allocationLimit = http2Connection.maxConcurrentStreams();
        }
      }
    }
    

    connectSocket() 함수로 들어가 보겠습니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
      Proxy proxy = route.proxy();
      Address address = route.address();
    
      //  socket, true
      rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
          ? address.socketFactory().createSocket()
          : new Socket(proxy);
    
      rawSocket.setSoTimeout(readTimeout);
      try {
        //  socket, 
        /**
        *    socket.connect(address, connectTimeout);
        *
        */
        Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
      } catch (ConnectException e) {
        ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
        ce.initCause(e);
        throw ce;
      }
      //  / 
      source = Okio.buffer(Okio.source(rawSocket));
      sink = Okio.buffer(Okio.sink(rawSocket));
    }
    

    https 프로토콜을 사용하고 실제 설정이 있으면 프로토콜이 업그레이드됩니다.
    Https 프로토콜 작성
    https 프로토콜 socket 연결이 완료된 후에도 한 걸음 더 가면 Tls 처리입니다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    
    private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
      Address address = route.address();
      SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
      boolean success = false;
      SSLSocket sslSocket = null;
      try {
        //  socket ssl
        // Create the wrapper over the connected socket.
        sslSocket = (SSLSocket) sslSocketFactory.createSocket(
            rawSocket, address.url().host(), address.url().port(), true /* autoClose */);
    
        // Configure the socket's ciphers, TLS versions, and extensions.
        ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
        if (connectionSpec.supportsTlsExtensions()) {
          Platform.get().configureTlsExtensions(
              sslSocket, address.url().host(), address.protocols());
        }
    
        // Force handshake. This can throw!
        sslSocket.startHandshake();
        Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
    
        // Verify that the socket's certificates are acceptable for the target host.
        if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
          X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
          throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
              + "
    certificate: " + CertificatePinner.pin(cert) + "
    DN: " + cert.getSubjectDN().getName() + "
    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert)); } // Check that the certificate pinner is satisfied by the certificates presented. address.certificatePinner().check(address.url().host(), unverifiedHandshake.peerCertificates()); // Success! Save the handshake and the ALPN protocol. String maybeProtocol = connectionSpec.supportsTlsExtensions() ? Platform.get().getSelectedProtocol(sslSocket) : null; socket = sslSocket; source = Okio.buffer(Okio.source(socket)); sink = Okio.buffer(Okio.sink(socket)); handshake = unverifiedHandshake; protocol = maybeProtocol != null ? Protocol.get(maybeProtocol) : Protocol.HTTP_1_1; success = true; } catch (AssertionError e) { if (Util.isAndroidGetsocknameError(e)) throw new IOException(e); throw e; } finally { if (sslSocket != null) { Platform.get().afterHandshake(sslSocket); } if (!success) { closeQuietly(sslSocket); } } }

    https에 익숙하다면 https는 http를 바탕으로 한 층을 더한 것을 알아야 한다.
    RealConnection 인스턴스를 완료했습니다.
    그리고 다음 차단기를 진행합니다.
    총결산
    이 차단기는 원리 절차가 비교적 간단한데 중점은 소켓과 Http 프로토콜에 관한 지식을 명확하게 이해하는 것이다.이 글의 지식은 벽돌을 던져 옥을 끌어올리는데 안의 일부 세부 사항은 말하지 않았기 때문에 독자들이 이 시리즈의 문장을 스스로 정리하는 것을 건의합니다.

    좋은 웹페이지 즐겨찾기