All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.okhttp3.internal.platform.Jdk8WithJettyBootPlatform.kt Maven / Gradle / Ivy

There is a newer version: 5.0.0-alpha.14
Show newest version
/*
 * Copyright (C) 2016 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okhttp3.internal.platform

import java.lang.reflect.InvocationHandler
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import javax.net.ssl.SSLSocket
import okhttp3.Protocol

/** OpenJDK 8 with `org.mortbay.jetty.alpn:alpn-boot` in the boot class path. */
class Jdk8WithJettyBootPlatform(
  private val putMethod: Method,
  private val getMethod: Method,
  private val removeMethod: Method,
  private val clientProviderClass: Class<*>,
  private val serverProviderClass: Class<*>,
) : Platform() {
  override fun configureTlsExtensions(
    sslSocket: SSLSocket,
    hostname: String?,
    protocols: List,
  ) {
    val names = alpnProtocolNames(protocols)

    try {
      val alpnProvider =
        Proxy.newProxyInstance(
          Platform::class.java.classLoader,
          arrayOf(clientProviderClass, serverProviderClass),
          AlpnProvider(names),
        )
      putMethod.invoke(null, sslSocket, alpnProvider)
    } catch (e: InvocationTargetException) {
      throw AssertionError("failed to set ALPN", e)
    } catch (e: IllegalAccessException) {
      throw AssertionError("failed to set ALPN", e)
    }
  }

  override fun afterHandshake(sslSocket: SSLSocket) {
    try {
      removeMethod.invoke(null, sslSocket)
    } catch (e: IllegalAccessException) {
      throw AssertionError("failed to remove ALPN", e)
    } catch (e: InvocationTargetException) {
      throw AssertionError("failed to remove ALPN", e)
    }
  }

  override fun getSelectedProtocol(sslSocket: SSLSocket): String? {
    try {
      val provider = Proxy.getInvocationHandler(getMethod.invoke(null, sslSocket)) as AlpnProvider
      if (!provider.unsupported && provider.selected == null) {
        log("ALPN callback dropped: HTTP/2 is disabled. " + "Is alpn-boot on the boot class path?")
        return null
      }
      return if (provider.unsupported) null else provider.selected
    } catch (e: InvocationTargetException) {
      throw AssertionError("failed to get ALPN selected protocol", e)
    } catch (e: IllegalAccessException) {
      throw AssertionError("failed to get ALPN selected protocol", e)
    }
  }

  /**
   * Handle the methods of ALPN's ClientProvider and ServerProvider without a compile-time
   * dependency on those interfaces.
   */
  private class AlpnProvider(
    /** This peer's supported protocols. */
    private val protocols: List,
  ) : InvocationHandler {
    /** Set when remote peer notifies ALPN is unsupported. */
    var unsupported: Boolean = false

    /** The protocol the server selected. */
    var selected: String? = null

    @Throws(Throwable::class)
    override fun invoke(
      proxy: Any,
      method: Method,
      args: Array?,
    ): Any? {
      val callArgs = args ?: arrayOf()
      val methodName = method.name
      val returnType = method.returnType
      if (methodName == "supports" && Boolean::class.javaPrimitiveType == returnType) {
        return true // ALPN is supported.
      } else if (methodName == "unsupported" && Void.TYPE == returnType) {
        this.unsupported = true // Peer doesn't support ALPN.
        return null
      } else if (methodName == "protocols" && callArgs.isEmpty()) {
        return protocols // Client advertises these protocols.
      } else if ((methodName == "selectProtocol" || methodName == "select") &&
        String::class.java == returnType && callArgs.size == 1 && callArgs[0] is List<*>
      ) {
        val peerProtocols = callArgs[0] as List<*>
        // Pick the first known protocol the peer advertises.
        for (i in 0..peerProtocols.size) {
          val protocol = peerProtocols[i] as String
          if (protocol in protocols) {
            selected = protocol
            return selected
          }
        }
        selected = protocols[0] // On no intersection, try peer's first protocol.
        return selected
      } else if ((methodName == "protocolSelected" || methodName == "selected") && callArgs.size == 1) {
        this.selected = callArgs[0] as String // Server selected this protocol.
        return null
      } else {
        return method.invoke(this, *callArgs)
      }
    }
  }

  companion object {
    fun buildIfSupported(): Platform? {
      val jvmVersion = System.getProperty("java.specification.version", "unknown")
      try {
        // 1.8, 9, 10, 11, 12 etc
        val version = jvmVersion.toInt()
        if (version >= 9) return null
      } catch (_: NumberFormatException) {
        // expected on >= JDK 9
      }

      // Find Jetty's ALPN extension for OpenJDK.
      try {
        val alpnClassName = "org.eclipse.jetty.alpn.ALPN"
        val alpnClass = Class.forName(alpnClassName, true, null)
        val providerClass = Class.forName("$alpnClassName\$Provider", true, null)
        val clientProviderClass = Class.forName("$alpnClassName\$ClientProvider", true, null)
        val serverProviderClass = Class.forName("$alpnClassName\$ServerProvider", true, null)
        val putMethod = alpnClass.getMethod("put", SSLSocket::class.java, providerClass)
        val getMethod = alpnClass.getMethod("get", SSLSocket::class.java)
        val removeMethod = alpnClass.getMethod("remove", SSLSocket::class.java)
        return Jdk8WithJettyBootPlatform(
          putMethod,
          getMethod,
          removeMethod,
          clientProviderClass,
          serverProviderClass,
        )
      } catch (_: ClassNotFoundException) {
      } catch (_: NoSuchMethodException) {
      }

      return null
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy