Skip to content

Commit

Permalink
Merge pull request #619 from hexagonkt/develop
Browse files Browse the repository at this point in the history
Use same model and handlers for HTTP client and server
  • Loading branch information
jaguililla authored Apr 16, 2023
2 parents 4dc8a3f + f24e240 commit 48704ba
Show file tree
Hide file tree
Showing 147 changed files with 2,804 additions and 1,612 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ jobs:
build:
name: Build
permissions: read-all
uses: hexagonkt/.github/.github/workflows/gradle.yml@master
uses: hexagonkt/.github/.github/workflows/graalvm_gradle.yml@master
with:
check_directory: core/build
35 changes: 30 additions & 5 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Nightly

on:
schedule:
- cron: "59 23 * * 0,2,4,6"
- cron: "59 23 * * *"

jobs:
stale:
Expand All @@ -25,16 +25,27 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
os: [ ubuntu-latest, windows-latest ]

name: Build (${{ matrix.os }})
permissions: read-all
uses: hexagonkt/.github/.github/workflows/gradle.yml@master
uses: hexagonkt/.github/.github/workflows/graalvm_gradle.yml@master
with:
os: ${{ matrix.os }}
check_directory: core/build
ref: develop

# TODO Delete this special case when Jacoco runs on macOS properly
build_macos:
name: Build (macos-latest)
permissions: read-all
uses: hexagonkt/.github/.github/workflows/graalvm_gradle.yml@master
with:
os: macos-latest
check_directory: core/build
ref: develop
tasks: -x jacocoTestReport

sample_keystores:
strategy:
fail-fast: false
Expand All @@ -43,12 +54,26 @@ jobs:

name: Sample Keystores (${{ matrix.os }})
permissions: read-all
uses: hexagonkt/.github/.github/workflows/gradle.yml@master
uses: hexagonkt/.github/.github/workflows/graalvm_gradle.yml@master
with:
os: ${{ matrix.os }}
ref: develop
tasks: createCa createIdentities

native_test:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]

name: Native Test
permissions: read-all
uses: hexagonkt/.github/.github/workflows/graalvm_gradle.yml@master
with:
os: ${{ matrix.os }}
ref: develop
tasks: nativeTest

build_site:
name: Build Site
permissions: read-all
Expand All @@ -64,7 +89,7 @@ jobs:

name: Test Publishing (${{ matrix.os }})
permissions: read-all
uses: hexagonkt/.github/.github/workflows/gradle.yml@master
uses: hexagonkt/.github/.github/workflows/graalvm_gradle.yml@master
with:
os: ${{ matrix.os }}
ref: develop
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ jobs:
sample_keystores:
name: Sample Keystores
permissions: read-all
uses: hexagonkt/.github/.github/workflows/gradle.yml@master
uses: hexagonkt/.github/.github/workflows/graalvm_gradle.yml@master
with:
tasks: createCa createIdentities

native_test:
name: Native Test
permissions: read-all
uses: hexagonkt/.github/.github/workflows/graalvm_gradle.yml@master
with:
tasks: nativeTest

build_site:
name: Build Site
permissions: read-all
Expand All @@ -20,7 +27,7 @@ jobs:
test_publishing:
name: Test Publishing
permissions: read-all
uses: hexagonkt/.github/.github/workflows/gradle.yml@master
uses: hexagonkt/.github/.github/workflows/graalvm_gradle.yml@master
with:
check_directory: core/build
tasks: publishToMavenLocal -x test
5 changes: 3 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set Up Java
uses: actions/setup-java@v3
uses: graalvm/setup-graalvm@v1
with:
version: latest
java-version: "17"
distribution: temurin
github-token: ${{ secrets.GITHUB_TOKEN }}
cache: gradle

- name: Update Site
Expand Down
10 changes: 3 additions & 7 deletions .github/workflows/site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ on:
description: Java version used to run Gradle.
default: "17"

java_distribution:
type: string
description: Java distribution used to run Gradle.
default: temurin

check_directory:
type: string
description: Directory to check for build execution.
Expand Down Expand Up @@ -46,10 +41,11 @@ jobs:
ref: ${{ inputs.ref }}

- name: Set Up Java
uses: actions/setup-java@v3
uses: graalvm/setup-graalvm@v1
with:
version: latest
java-version: ${{ inputs.java }}
distribution: ${{ inputs.java_distribution }}
github-token: ${{ secrets.GITHUB_TOKEN }}
cache: gradle

- name: Build Project
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ org.gradle.warning.mode=all
org.gradle.console=plain

# Gradle
version=2.7.0
version=2.8.0
group=com.hexagonkt
description=The atoms of your platform

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ open class JmhBenchmark {
OnHandler { it.appendText("<B2>")},
)

@Benchmark fun before_and_after_handlers_are_called_in_order(bh: Blackhole) {
@Benchmark fun beforeAndAfterHandlersAreCalledInOrder(bh: Blackhole) {

// No handler called
chains.forEach {
Expand Down Expand Up @@ -121,7 +121,7 @@ open class JmhBenchmark {
}
}

@Benchmark fun filters_allow_passing_and_halting(bh: Blackhole) {
@Benchmark fun filtersAllowPassingAndHalting(bh: Blackhole) {
// Filter passing
bh.consume(filtersChain.process("a"))
// Filter halting
Expand Down
7 changes: 6 additions & 1 deletion http/src/main/kotlin/com/hexagonkt/http/Http.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ val GMT_ZONE: ZoneId = ZoneId.of("GMT")

val HTTP_DATE_FORMATTER: DateTimeFormatter = RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC)

val BODY_TYPES = setOf(String::class, ByteArray::class, Int::class, Long::class)

val BODY_TYPES_NAMES = BODY_TYPES.joinToString(", ") { it.simpleName.toString() }

fun checkHeaders(headers: Headers) {
if (!assertEnabled)
return
Expand Down Expand Up @@ -120,5 +124,6 @@ fun bodyToBytes(body: Any): ByteArray =
is ByteArray -> body
is Int -> BigInteger.valueOf(body.toLong()).toByteArray()
is Long -> BigInteger.valueOf(body).toByteArray()
else -> error("Unsupported body type: ${body.javaClass.simpleName}")
else ->
error("Unsupported body type: ${body.javaClass.simpleName}. Must be: $BODY_TYPES_NAMES")
}
5 changes: 2 additions & 3 deletions http/src/main/kotlin/com/hexagonkt/http/model/HttpBase.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.hexagonkt.http.model

interface HttpBase {
// TODO Allow get by chunks with 'channel/flow'
val body: Any
val headers: Headers // ["H"] // value of "H" header
val contentType: ContentType? // media type of request.body
val headers: Headers
val contentType: ContentType?

fun bodyString(): String =
when (body) {
Expand Down
8 changes: 4 additions & 4 deletions http/src/main/kotlin/com/hexagonkt/http/model/HttpCall.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.hexagonkt.http.model

interface HttpCall<I : HttpRequest, O : HttpResponse> {
val request: I
val response: O
}
data class HttpCall(
val request: HttpRequestPort = HttpRequest(),
val response: HttpResponsePort = HttpResponse(),
)
96 changes: 59 additions & 37 deletions http/src/main/kotlin/com/hexagonkt/http/model/HttpRequest.kt
Original file line number Diff line number Diff line change
@@ -1,43 +1,65 @@
package com.hexagonkt.http.model

import com.hexagonkt.http.formatQueryString
import java.net.URL
import com.hexagonkt.http.*
import com.hexagonkt.http.model.HttpMethod.GET
import com.hexagonkt.http.model.HttpProtocol.HTTP
import java.security.cert.X509Certificate

// TODO 'formParameters' are a kind of 'part' and both are handled as part of the 'body'
// they could be handled as a special kind of type in body processing (List<HttpPartPort>)
interface HttpRequest : HttpMessage {
val method: HttpMethod // "GET"
val protocol: HttpProtocol // "http"
val host: String // "example.com"
val port: Int // 80
val path: String // "/foo" servlet path + path info
val queryParameters: QueryParameters
val parts: List<HttpPart> // hash of multipart parts
val formParameters: FormParameters
val accept: List<ContentType>
val authorization: Authorization?
data class HttpRequest(
override val method: HttpMethod = GET,
override val protocol: HttpProtocol = HTTP,
override val host: String = "localhost",
override val port: Int = 80,
override val path: String = "",
override val queryParameters: QueryParameters = QueryParameters(),
override val headers: Headers = Headers(),
override val body: Any = "",
override val parts: List<HttpPart> = emptyList(),
override val formParameters: FormParameters = FormParameters(),
override val cookies: List<Cookie> = emptyList(),
override val contentType: ContentType? = null,
override val certificateChain: List<X509Certificate> = emptyList(),
override val accept: List<ContentType> = emptyList(),
override val contentLength: Long = -1L,
override val authorization: Authorization? = null,
) : HttpRequestPort {

fun partsMap(): Map<String, HttpPart> =
parts.associateBy { it.name }
init {
checkHeaders(headers)
}

fun url(): URL =
if (queryParameters.isEmpty())
URL("${protocol.schema}://$host:$port/$path")
else
URL("${protocol.schema}://$host:$port/$path?${formatQueryString(queryParameters)}")

fun userAgent(): String? =
headers["user-agent"]?.value

fun referer(): String? =
headers["referer"]?.value

fun origin(): String? =
headers["origin"]?.value

fun authorization(): Authorization? =
headers["authorization"]
?.value
?.split(" ", limit = 2)
?.let { Authorization(it.first(), it.last()) }
override fun with(
body: Any,
headers: Headers,
contentType: ContentType?,
method: HttpMethod,
protocol: HttpProtocol,
host: String,
port: Int,
path: String,
queryParameters: QueryParameters,
parts: List<HttpPart>,
formParameters: FormParameters,
cookies: List<Cookie>,
accept: List<ContentType>,
authorization: Authorization?,
certificateChain: List<X509Certificate>,
): HttpRequestPort =
copy(
body = body,
headers = headers,
contentType = contentType,
method = method,
protocol = protocol,
host = host,
port = port,
path = path,
queryParameters = queryParameters,
parts = parts,
formParameters = formParameters,
cookies = cookies,
accept = accept,
authorization = authorization,
certificateChain = certificateChain,
)
}
70 changes: 70 additions & 0 deletions http/src/main/kotlin/com/hexagonkt/http/model/HttpRequestPort.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.hexagonkt.http.model

import com.hexagonkt.http.formatQueryString
import java.net.URL
import java.security.cert.X509Certificate

// TODO 'formParameters' are a kind of 'part' and both are handled as part of the 'body'
// they could be handled as a special kind of type in body processing (List<HttpPartPort>)
interface HttpRequestPort : HttpMessage {
val method: HttpMethod // "GET"
val protocol: HttpProtocol // "http"
val host: String // "example.com"
val port: Int // 80
val path: String // "/foo" servlet path + path info
val queryParameters: QueryParameters
val parts: List<HttpPart> // hash of multipart parts
val formParameters: FormParameters
val accept: List<ContentType>
val authorization: Authorization?

val certificateChain: List<X509Certificate>
val contentLength: Long // length of request.body (or 0)

fun with(
body: Any = this.body,
headers: Headers = this.headers,
contentType: ContentType? = this.contentType,
method: HttpMethod = this.method,
protocol: HttpProtocol = this.protocol,
host: String = this.host,
port: Int = this.port,
path: String = this.path,
queryParameters: QueryParameters = this.queryParameters,
parts: List<HttpPart> = this.parts,
formParameters: FormParameters = this.formParameters,
cookies: List<Cookie> = this.cookies,
accept: List<ContentType> = this.accept,
authorization: Authorization? = this.authorization,
certificateChain: List<X509Certificate> = this.certificateChain,
): HttpRequestPort

fun certificate(): X509Certificate? =
certificateChain.firstOrNull()

fun partsMap(): Map<String, HttpPart> =
parts.associateBy { it.name }

fun url(): URL =
when {
queryParameters.isEmpty() && port == 80 -> "${protocol.schema}://$host/$path"
queryParameters.isEmpty() -> "${protocol.schema}://$host:$port/$path"
else -> "${protocol.schema}://$host:$port/$path?${formatQueryString(queryParameters)}"
}
.let(::URL)

fun userAgent(): String? =
headers["user-agent"]?.value

fun referer(): String? =
headers["referer"]?.value

fun origin(): String? =
headers["origin"]?.value

fun authorization(): Authorization? =
headers["authorization"]
?.value
?.split(" ", limit = 2)
?.let { Authorization(it.first(), it.last()) }
}
Loading

0 comments on commit 48704ba

Please sign in to comment.