Skip to content

Commit

Permalink
kotlin: add library request interfaces (#240)
Browse files Browse the repository at this point in the history
envoyproxy/envoy-mobile#119

Adding a primitive `Request` and `RequestBuilder` with basic features:
* URL
* method
* headers
* trailers
* retry policy

Tests are basic setting a builder and ensuring the result is expected. Additionally, the request build transform creates an equivalent object when built back.

Signed-off-by: Alan Chiu <achiu@lyft.com>

For an explanation of how to fill out the fields, please see the relevant section
in [PULL_REQUESTS.md](https://github.com/envoyproxy/envoy/blob/master/PULL_REQUESTS.md)

Description: kotlin: add library request interfaces
Risk Level: low
Testing: unit
Docs Changes: n/a
Release Notes: n/a
[Optional Fixes #Issue]
[Optional Deprecated:]

Signed-off-by: JP Simard <jp@jpsim.com>
  • Loading branch information
Alan Chiu authored and jpsim committed Nov 29, 2022
1 parent 152f48c commit a44cec2
Show file tree
Hide file tree
Showing 8 changed files with 521 additions and 1 deletion.
12 changes: 11 additions & 1 deletion mobile/library/kotlin/src/io/envoyproxy/envoymobile/BUILD
Expand Up @@ -2,7 +2,7 @@ licenses(["notice"]) # Apache 2

load("@envoy//bazel:envoy_build_system.bzl", "envoy_package")
load("//bazel:aar_with_jni.bzl", "aar_with_jni")
load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library")
load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library", "kt_jvm_library")

envoy_package()

Expand Down Expand Up @@ -30,3 +30,13 @@ kt_android_library(
visibility = ["//visibility:public"],
deps = ["//library/java/io/envoyproxy/envoymobile/engine:envoy_engine_lib"],
)

kt_jvm_library(
name = "envoy_interfaces_lib",
srcs = [
"Request.kt",
"RequestBuilder.kt",
"RequestMethod.kt",
"RetryPolicy.kt",
],
)
56 changes: 56 additions & 0 deletions mobile/library/kotlin/src/io/envoyproxy/envoymobile/Request.kt
@@ -0,0 +1,56 @@
package io.envoyproxy.envoymobile

import java.net.URL

class Request internal constructor(
val method: RequestMethod,
val url: URL,
val headers: Map<String, List<String>>,
val trailers: Map<String, List<String>>,
val body: ByteArray?,
val retryPolicy: RetryPolicy?
) {

/**
* Transforms this Request to the {@link io.envoyproxy.envoymobile.RequestBuilder} for modification using the
* current properties
*
* @return the builder
*/
fun toBuilder(): RequestBuilder {
return RequestBuilder(url, method)
.setHeaders(headers)
.setTrailers(trailers)
.addBody(body)
.addRetryPolicy(retryPolicy)
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as Request

if (method != other.method) return false
if (url != other.url) return false
if (headers != other.headers) return false
if (trailers != other.trailers) return false
if (body != null) {
if (other.body == null) return false
if (!body.contentEquals(other.body)) return false
} else if (other.body != null) return false
if (retryPolicy != other.retryPolicy) return false

return true
}

override fun hashCode(): Int {
var result = method.hashCode()
result = 31 * result + url.hashCode()
result = 31 * result + headers.hashCode()
result = 31 * result + trailers.hashCode()
result = 31 * result + (body?.contentHashCode() ?: 0)
result = 31 * result + (retryPolicy?.hashCode() ?: 0)
return result
}
}
170 changes: 170 additions & 0 deletions mobile/library/kotlin/src/io/envoyproxy/envoymobile/RequestBuilder.kt
@@ -0,0 +1,170 @@
package io.envoyproxy.envoymobile

import java.net.URL


/**
* Builder used for constructing instances of `Request` types.
*
* @param url URL for the request.
* @param method Method for the request.
*/
class RequestBuilder(
val url: URL,
val method: RequestMethod
) {
// Headers to send with the request.
// Multiple values for a given name are valid, and will be sent as comma-separated values.
private val headers: MutableMap<String, MutableList<String>> = mutableMapOf()

// Trailers to send with the request.
// Multiple values for a given name are valid, and will be sent as comma-separated values.
private val trailers: MutableMap<String, MutableList<String>> = mutableMapOf()

// Serialized data to send as the body of the request.
private var body: ByteArray? = null

// Retry policy to use for this request.
private var retryPolicy: RetryPolicy? = null

/**
* Serialized data to send as the body of the request.
*/
fun addBody(body: ByteArray?): RequestBuilder {
this.body = body
return this
}

/**
* Add a retry policy to use for this request.
*
* @param retryPolicy the {@link io.envoyproxy.envoymobile.RetryPolicy} for this request.
* @return this builder.
*/
fun addRetryPolicy(retryPolicy: RetryPolicy?): RequestBuilder {
this.retryPolicy = retryPolicy
return this
}

/**
* Append a value to the header key.
*
* @param name the header key.
* @param value the value associated to the header key.
* @return this builder.
*/
fun addHeader(name: String, value: String): RequestBuilder {
if (headers.containsKey(name)) {
headers[name]!!.add(value)
} else {
headers[name] = mutableListOf(value)
}
return this
}

/**
* Remove the value in the specified header.
*
* @param name the header key to remove.
* @param value the value to be removed.
* @return this builder.
*/
fun removeHeader(name: String, value: String): RequestBuilder {
if (headers.containsKey(name)) {
headers[name]!!.remove(value)
if (headers[name]!!.isEmpty()) {
headers.remove(name)
}
}
return this
}

/**
* Remove all headers with this name.
*
* @param name the header key to remove.
* @return this builder.
*/
fun removeHeaders(name: String): RequestBuilder {
headers.remove(name)
return this
}

/**
* Append a value to the trailer key.
*
* @param name the trailer key.
* @param value the value associated to the trailer key.
* @return this builder.
*/
fun addTrailer(name: String, value: String): RequestBuilder {
if (trailers.containsKey(name)) {
trailers[name]!!.add(value)
} else {
trailers[name] = mutableListOf(value)
}
return this
}

/**
* Remove the value in the specified trailer.
*
* @param name the trailer key to remove.
* @param value the value to be removed.
* @return this builder.
*/
fun removeTrailers(name: String): RequestBuilder {
trailers.remove(name)
return this
}

/**
* Remove the value in the specified trailer.
*
* @param name the trailer key to remove.
* @param value the value to be removed.
* @return this builder.
*/
fun removeTrailer(name: String, value: String): RequestBuilder {
if (trailers.containsKey(name)) {
trailers[name]!!.remove(value)

if (trailers[name]!!.isEmpty()) {
trailers.remove(name)
}
}
return this
}

/**
* Creates the {@link io.envoyproxy.envoymobile.Request} object using the data set in the builder
*
* @return the {@link io.envoyproxy.envoymobile.Request} object
*/
fun build(): Request {
return Request(
method,
url,
headers,
trailers,
body,
retryPolicy
)
}

internal fun setHeaders(headers: Map<String, List<String>>): RequestBuilder {
this.headers.clear()
for (entry in headers) {
this.headers[entry.key] = entry.value.toMutableList()
}
return this
}

internal fun setTrailers(trailers: Map<String, List<String>>): RequestBuilder {
this.trailers.clear()
for (entry in trailers) {
this.trailers[entry.key] = entry.value.toMutableList()
}
return this
}
}
@@ -0,0 +1,15 @@
package io.envoyproxy.envoymobile

/**
* Represents an HTTP request method.
*/
enum class RequestMethod {
DELETE,
GET,
HEAD,
OPTIONS,
PATCH,
POST,
PUT,
TRACE
}
26 changes: 26 additions & 0 deletions mobile/library/kotlin/src/io/envoyproxy/envoymobile/RetryPolicy.kt
@@ -0,0 +1,26 @@
package io.envoyproxy.envoymobile

/**
* Specifies how a request may be retried, containing one or more rules.
*
* @param maxRetryCount Maximum number of retries that a request may be performed.
* @param retryOn Whitelist of rules used for retrying.
* @param perRetryTimeoutMs Timeout (in milliseconds) to apply to each retry.
*/
data class RetryPolicy(
val maxRetryCount: Int,
val retryOn: List<RetryRule>,
val perRetryTimeoutMs: Long?
)

/**
* These are retry rules specified in Envoy's router filter.
* @see <a href="https://www.envoyproxy.io/docs/envoy/latest/configuration/http_filters/router_filter#x-envoy-retry-on">x-envoy-retry-on</a>
*/
enum class RetryRule {
FIVE_XX,
GATEWAY_ERROR,
CONNECT_FAILURE,
RETRIABLE_FOUR_XX,
REFUSED_UPSTREAM,
}
26 changes: 26 additions & 0 deletions mobile/library/kotlin/test/io/envoyproxy/envoymobile/BUILD
@@ -0,0 +1,26 @@
licenses(["notice"]) # Apache 2

load("@envoy//bazel:envoy_build_system.bzl", "envoy_package")
load("//bazel:kotlin_test.bzl", "envoy_mobile_kt_test")

envoy_package()

envoy_mobile_kt_test(
name = "request_builder_test",
srcs = [
"RequestBuilderTest.kt",
],
deps = [
"//library/kotlin/src/io/envoyproxy/envoymobile:envoy_interfaces_lib",
],
)

envoy_mobile_kt_test(
name = "request_test",
srcs = [
"RequestTest.kt",
],
deps = [
"//library/kotlin/src/io/envoyproxy/envoymobile:envoy_interfaces_lib",
],
)

0 comments on commit a44cec2

Please sign in to comment.