我正在尝试在 Kotlin(适用于 Android)中实现 HTTP/HTTPS 代理服务器,对于 HTTP,它按预期工作,但对于 HTTPS 请求,它始终会得到
The connection has timed out
。
与服务器的客户端连接已建立,但我在实现中没有抛出任何错误,这是代理服务器的完整代码:
interface ProxyPortListener {
fun setProxyPort(port: Int)
}
class ProxyServer(private val context: Context) : Thread() {
var countOfConnectionsDone = 0
companion object {
private const val TAG = "ProxyServer"
private const val CONNECT = "CONNECT"
private const val HTTP_OK = "HTTP/1.1 200 OK\r\n"
private const val HEADER_CONNECTION = "connection"
private const val HEADER_PROXY_CONNECTION = "proxy-connection"
}
private val threadExecutor: ExecutorService = Executors.newCachedThreadPool()
var isRunning: Boolean = false
private set
private var serverSocket: ServerSocket? = null
private var port: Int = -1
private var callback: ProxyPortListener? = null
init {
startServer()
}
fun setCallback(callback: ProxyPortListener) {
this.callback = callback
if (port != -1) {
callback.setProxyPort(port)
}
}
override fun run() {
try {
serverSocket =
ServerSocket(Prefs.getProxyPort(context), 0, InetAddress.getByName("0.0.0.0"))
port = serverSocket!!.localPort
callback?.setProxyPort(port)
isRunning = true
Log.d(TAG, "Proxy server started on port $port")
while (isRunning) {
try {
val socket = serverSocket!!.accept()
Log.d(TAG, "Accepted connection from ${socket.inetAddress}")
val proxyConnection = ProxyConnection(socket)
threadExecutor.execute(proxyConnection)
} catch (e: SocketException) {
Log.e(TAG, "Socket exception while running server", e)
}
}
} catch (e: IOException) {
Log.e(TAG, "Failed to start proxy server", e)
}
}
fun startServer() {
if (!isRunning && state == Thread.State.NEW) {
start()
}
}
fun stopServer() {
isRunning = false
serverSocket?.close()
serverSocket = null
try {
join() // Wait for the thread to finish executing
} catch (e: InterruptedException) {
Log.e(TAG, "Failed to stop proxy server", e)
}
}
private inner class ProxyConnection(private val connection: Socket) : Runnable {
override fun run() {
try {
val requestLine = getLine(connection.getInputStream())
val splitLine = requestLine.split(" ")
if (splitLine.size < 3) {
connection.close()
return
}
val requestType = splitLine[0]
var urlString = splitLine[1]
val httpVersion = splitLine[2]
val url: URI
val host: String
val port: Int
var server: Socket? = null
if (requestType == CONNECT) {
// Handle HTTPS connections using CONNECT method
val parts = urlString.split(":")
host = parts[0]
port = if (parts.size > 1) parts[1].toInt() else 443
try {
server = Socket(host, port)
sendLine(connection, HTTP_OK)
SocketConnect.connect(connection, server)
} catch (e: IOException) {
Log.e(TAG, "Error handling CONNECT request", e)
connection.close()
}
return
} else {
// Handle HTTP connections
try {
url = URI(urlString)
host = url.host
port = if (url.port < 0) 80 else url.port
} catch (e: URISyntaxException) {
connection.close()
return
}
}
// Create socket based on proxy configuration
val proxy = ProxySelector.getDefault().select(URI(urlString)).firstOrNull()
if (proxy != Proxy.NO_PROXY) {
val inetSocketAddress = proxy?.address() as InetSocketAddress
server = Socket(inetSocketAddress.hostName, inetSocketAddress.port)
} else {
server = Socket(host, port)
}
// Forward request and response
sendAugmentedRequestToHost(connection, server, requestType, url, httpVersion)
SocketConnect.connect(connection, server)
} catch (e: Exception) {
Log.d(TAG, "Problem Proxying", e)
} finally {
try {
connection.close()
} catch (ioe: IOException) {
Log.e(TAG, "Error closing connection", ioe)
}
}
}
private fun sendAugmentedRequestToHost(
src: Socket,
dst: Socket,
httpMethod: String,
uri: URI,
httpVersion: String
) {
sendRequestLineWithPath(dst, httpMethod, uri, httpVersion)
filterAndForwardRequestHeaders(src, dst)
sendLine(dst, "Connection: close")
sendLine(dst, "")
}
private fun sendRequestLineWithPath(
server: Socket,
requestType: String,
absoluteUri: URI,
httpVersion: String
) {
val absolutePath = getAbsolutePathFromAbsoluteURI(absoluteUri)
val outgoingRequestLine = "$requestType $absolutePath $httpVersion"
sendLine(server, outgoingRequestLine)
}
private fun getAbsolutePathFromAbsoluteURI(uri: URI): String {
val rawPath = uri.rawPath ?: "/"
val rawQuery = uri.rawQuery?.let { "?$it" } ?: ""
val rawFragment = uri.rawFragment?.let { "#$it" } ?: ""
return "$rawPath$rawQuery$rawFragment"
}
private fun getLine(inputStream: InputStream): String {
val buffer = StringBuilder()
var byteBuffer = inputStream.read()
if (byteBuffer < 0) return ""
while (byteBuffer != '\n'.code && byteBuffer >= 0) {
if (byteBuffer != '\r'.code) {
buffer.append(byteBuffer.toChar())
}
byteBuffer = inputStream.read()
}
return buffer.toString()
}
private fun sendLine(socket: Socket, line: String) {
try {
if (!socket.isClosed) {
val os = socket.getOutputStream()
os.write(line.toByteArray())
os.write('\r'.code)
os.write('\n'.code)
os.flush()
} else {
Log.e(TAG, "Cannot write to closed socket")
}
} catch (e: IOException) {
Log.e(TAG, "Error while writing to socket: ${e.message}")
}
}
private fun filterAndForwardRequestHeaders(src: Socket, dst: Socket) {
var line: String
do {
line = getLine(src.getInputStream())
if (line.isNotEmpty() && !shouldRemoveHeaderLine(line)) {
sendLine(dst, line)
}
} while (line.isNotEmpty())
}
private fun shouldRemoveHeaderLine(line: String): Boolean {
val colIndex = line.indexOf(":")
if (colIndex != -1) {
val headerName = line.substring(0, colIndex).trim()
if (headerName.equals(HEADER_CONNECTION, ignoreCase = true)
|| headerName.equals(HEADER_PROXY_CONNECTION, ignoreCase = true)
) {
return true
}
}
return false
}
}
class SocketConnect(private val from: Socket, private val to: Socket) : Thread() {
private val buffer = ByteArray(512)
override fun run() {
val inputStream = from.getInputStream()
val outputStream = to.getOutputStream()
try {
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
}
} catch (ioe: IOException) {
// Do nothing
}
}
companion object {
fun connect(first: Socket, second: Socket) {
val sc1 = SocketConnect(first, second)
val sc2 = SocketConnect(second, first)
sc1.start()
sc2.start()
try {
sc1.join()
sc2.join()
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
}
}
这个超时错误可能是什么原因?
因此我创建了一个函数,可以在 CONNET 中传输数据(HTTPS 情况):
@Throws(IOException::class)
private fun skipToRequestBody(socket: Socket) {
while (getLine(socket.getInputStream()).isNotEmpty())
}
这里是调整后的运行函数:
override fun run() {
try {
countOfConnectionsDone = NetworkUtils.checkIfCountExceeded(countOfConnectionsDone, context)
val requestLine = getLine(connection.getInputStream())
val splitLine = requestLine.split(" ")
if (splitLine.size < 3) {
connection.close()
return
}
val requestType = splitLine[0]
var urlString = splitLine[1]
val httpVersion = splitLine[2]
val url: URI
val host: String
val port: Int
var server: Socket? = null
if (requestType == CONNECT) {
val hostPortSplit: List<String> = urlString.split(":")
host = hostPortSplit[0]
// Use default SSL port if not specified. Parse it otherwise
port = if (hostPortSplit.size < 2) {
443
} else {
try {
hostPortSplit[1].toInt()
} catch (nfe: NumberFormatException) {
connection.close()
return
}
}
urlString = "Https://" + attr.host + ":" + port
} else {
// Handle HTTP connections
try {
url = URI(urlString)
host = url.host
port = if (url.port < 0) 80 else url.port
} catch (e: URISyntaxException) {
connection.close()
return
}
}
// Create socket based on proxy configuration
val proxy = ProxySelector.getDefault().select(URI(urlString)).firstOrNull()
if (proxy != Proxy.NO_PROXY) {
val inetSocketAddress = proxy?.address() as InetSocketAddress
server = Socket(inetSocketAddress.hostName, inetSocketAddress.port)
} else {
server = Socket(host, port)
}
// Forward request and response
if (requestType == CONNECT) {
skipToRequestBody(connection);
// No proxy to respond so we must.
sendLine(connection, HTTP_OK);
} else {
sendAugmentedRequestToHost(connection, server, requestType, URI(urlString), httpVersion)
}
SocketConnect.connect(connection, server)
} catch (e: Exception) {
Log.d(TAG, "Problem Proxying", e)
} finally {
try {
connection.close()
} catch (ioe: IOException) {
Log.e(TAG, "Error closing connection", ioe)
}
}
}