-
Notifications
You must be signed in to change notification settings - Fork 102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add http transport #449
Add http transport #449
Changes from 1 commit
af78d7d
c89653b
78a05a3
d973500
a873829
b11d796
ac20ca9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you 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 com.microsoft.thrifty.transport | ||
|
||
|
||
import java.io.ByteArrayOutputStream | ||
import java.io.IOException | ||
import java.io.InputStream | ||
import java.net.HttpURLConnection | ||
import java.net.URL | ||
|
||
/** | ||
* HTTP implementation of the TTransport interface. Used for working with a | ||
* Thrift web services implementation (using for example TServlet). | ||
* | ||
* THIS IMPLEMENTATION IS NOT THREAD-SAFE !!! | ||
* | ||
* Based on the official thrift java THttpTransport with the apache client support removed. | ||
* Both due to wanting to avoid the additional dependency as well as it being a bit weird to have two | ||
* implementations to switch between in the same class. | ||
* | ||
* Uses HttpURLConnection internally | ||
* | ||
* Also note that under high load, the HttpURLConnection implementation | ||
* may exhaust the open file descriptor limit. | ||
* | ||
* @see [THRIFT-970](https://issues.apache.org/jira/browse/THRIFT-970) | ||
*/ | ||
open class THttpTransport(url: String) : Transport { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, a substantive comment - can we please make an Generally on JVM I favor using OkHttp over HttpURLConnection - it's much easier to use correctly and scalably. Is there a reason, aside from the initial implementation using it, to favor HttpURLConnection? I suppose the fact that it's built in to the JDK is a point in its favor, along with not wanting to bring in extra dependencies. This is part of why I suggested making a separate module for this guy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What benefit do we get from making it an expect/actual class? Why can't there just be different types of Transports implemented for different variants? This is a genuine question, I have no experience with Kotlin Multiplatform. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, main reason is OOB-ness. I don't want to add something to the runtime that drags in a bunch of transitive dependencies. For that I would prefer the separate module. But I see nothing wrong with providing a basic HttpTransport with maybe not the best performance that needs no dependencies and is this simple as part of the "core" library. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair point re: "OOB-ness". My concern definitely is around the default easy path. Nothing's easier, dependency-wise, than what's built in. On the other hand, this is an Android library first and foremost, and on Android OkHttp is basically the standard anymore. I'm OK with leaving it out for now. As far as expect/actual, this is the mechanism that lets us have interfaces in common code but have platform-specific implementations. In the eventual iOS release, we'll definitely want to use an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was trying to understand the benefit of the So if I understand correctly, Please excuse all the text, I am mainly trying to wrap my head around Kotlin Multiplatform. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right that we could just have platform-specific types that implement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I never addressed you question about the benefit(s) of |
||
private val url: URL = URL(url) | ||
private var currentState: Transport = Writing() | ||
private var connectTimeout: Int? = null | ||
private var readTimeout: Int? = null | ||
private val customHeaders = mutableMapOf<String, String>() | ||
private val sendBuffer = ByteArrayOutputStream() | ||
|
||
private inner class Writing : Transport { | ||
override fun read(buffer: ByteArray, offset: Int, count: Int): Int { | ||
error("Currently in writing state") | ||
} | ||
|
||
override fun write(buffer: ByteArray, offset: Int, count: Int) { | ||
sendBuffer.write(buffer, offset, count) | ||
} | ||
|
||
override fun flush() { | ||
val bytesToSend = sendBuffer.toByteArray() | ||
sendBuffer.reset() | ||
send(bytesToSend) | ||
} | ||
|
||
override fun close() { | ||
// do nothing | ||
} | ||
} | ||
|
||
private inner class Reading(val inputStream: InputStream) : Transport { | ||
override fun read(buffer: ByteArray, offset: Int, count: Int): Int { | ||
val ret = try { | ||
inputStream.read(buffer, offset, count) | ||
} catch (iox: IOException) { | ||
throw TTransportException(iox) | ||
} | ||
if (ret == -1) { | ||
throw TTransportException("No more data available.") | ||
} | ||
return ret | ||
} | ||
|
||
override fun write(buffer: ByteArray, offset: Int, count: Int) { | ||
error("currently in reading state") | ||
} | ||
|
||
override fun flush() { | ||
error("currently in reading state") | ||
} | ||
|
||
override fun close() { | ||
inputStream.close() | ||
} | ||
} | ||
|
||
fun send(data: ByteArray) { | ||
try { | ||
// Create connection object | ||
val connection = url.openConnection() as HttpURLConnection | ||
|
||
prepareConnection(connection) | ||
// Make the request | ||
connection.connect() | ||
connection.outputStream.write(data) | ||
val responseCode = connection.responseCode | ||
if (responseCode != HttpURLConnection.HTTP_OK) { | ||
throw TTransportException("HTTP Response code: $responseCode") | ||
} | ||
|
||
// Read the response | ||
this.currentState = Reading(connection.inputStream) | ||
} catch (iox: IOException) { | ||
throw TTransportException(iox) | ||
} | ||
} | ||
|
||
protected open fun prepareConnection(connection: HttpURLConnection) { | ||
// Timeouts, only if explicitly set | ||
connectTimeout?.let { connection.connectTimeout = it } | ||
readTimeout?.let { connection.readTimeout = it } | ||
|
||
connection.requestMethod = "POST" | ||
connection.setRequestProperty("Content-Type", "application/x-thrift") | ||
connection.setRequestProperty("Accept", "application/x-thrift") | ||
connection.setRequestProperty("User-Agent", "Java/THttpClient") | ||
for ((key, value) in customHeaders) { | ||
connection.setRequestProperty(key, value) | ||
} | ||
connection.doOutput = true | ||
} | ||
|
||
fun setConnectTimeout(timeout: Int) { | ||
connectTimeout = timeout | ||
} | ||
|
||
fun setReadTimeout(timeout: Int) { | ||
readTimeout = timeout | ||
} | ||
|
||
fun setCustomHeaders(headers: Map<String, String>) { | ||
customHeaders.clear() | ||
customHeaders.putAll(headers) | ||
} | ||
|
||
fun setCustomHeader(key: String, value: String) { | ||
customHeaders[key] = value | ||
} | ||
|
||
override fun close() { | ||
currentState.close() | ||
} | ||
|
||
override fun read(buffer: ByteArray, offset: Int, count: Int): Int = currentState.read(buffer, offset, count) | ||
|
||
override fun write(buffer: ByteArray, offset: Int, count: Int) { | ||
// this mirrors the original behaviour, though it is not very elegant. | ||
// we don't know when the user is done reading, so when they start writing again, | ||
// we just go with it. | ||
if (currentState is Reading) { | ||
currentState.close() | ||
currentState = Writing() | ||
} | ||
currentState.write(buffer, offset, count) | ||
} | ||
|
||
override fun flush() { | ||
currentState.flush() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.microsoft.thrifty.transport | ||
|
||
class TTransportException : Throwable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tend to think this exception doesn't buy us anything, and would prefer not to include it. We could use the perfectly-good There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, SGTM. |
||
constructor(message: String?) : super(message) | ||
constructor(cause: Throwable?) : super(cause) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's petty, but let's drop the
T
prefix. It's just ugly to me and, is out-of-step with the rest of the project, and (perhaps most importantly) doesn't deliver any actual information.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For context, the
CompactProtocol
class specifically is heavily based onTCompactProtocol
but even there, the prefix had to go.TType
retains its prefix only becauseType
is taken, and "T for Thrift" actually makes sense there.