Add HTTP module

Signed-off-by: Graham <gpe@openrs2.org>
pull/132/head
Graham 3 years ago
parent 86e9963a75
commit 5da59135ea
  1. 30
      http/build.gradle.kts
  2. 16
      http/src/main/kotlin/org/openrs2/http/HttpClientProvider.kt
  3. 13
      http/src/main/kotlin/org/openrs2/http/HttpModule.kt
  4. 13
      http/src/main/kotlin/org/openrs2/http/HttpResponseExtensions.kt
  5. 39
      http/src/main/kotlin/org/openrs2/http/NetrcAuthenticator.kt
  6. 95
      http/src/main/kotlin/org/openrs2/http/NetrcReader.kt
  7. 1
      settings.gradle.kts

@ -0,0 +1,30 @@
plugins {
`maven-publish`
kotlin("jvm")
}
dependencies {
api("com.google.inject:guice:${Versions.guice}")
implementation("com.google.guava:guava:${Versions.guava}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlinCoroutines}")
}
publishing {
publications.create<MavenPublication>("maven") {
from(components["java"])
pom {
packaging = "jar"
name.set("OpenRS2 HTTP")
description.set(
"""
Guava module for creating a HTTP client with .netrc and
redirection support. The I/O dispatcher is used to run
asynchronous requests, as code using coroutines will likely use
the I/O dispatcher to read the response body.
""".trimIndent()
)
}
}
}

@ -0,0 +1,16 @@
package org.openrs2.http
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import java.net.http.HttpClient
import javax.inject.Provider
public class HttpClientProvider : Provider<HttpClient> {
override fun get(): HttpClient {
return HttpClient.newBuilder()
.authenticator(NetrcAuthenticator.read())
.executor(Dispatchers.IO.asExecutor())
.followRedirects(HttpClient.Redirect.NORMAL)
.build()
}
}

@ -0,0 +1,13 @@
package org.openrs2.http
import com.google.inject.AbstractModule
import com.google.inject.Scopes
import java.net.http.HttpClient
public object HttpModule : AbstractModule() {
override fun configure() {
bind(HttpClient::class.java)
.toProvider(HttpClientProvider::class.java)
.`in`(Scopes.SINGLETON)
}
}

@ -0,0 +1,13 @@
package org.openrs2.http
import java.io.IOException
import java.net.http.HttpResponse
private val DEFAULT_EXPECTED_STATUS_CODES = setOf(200)
public fun <T> HttpResponse<T>.checkStatusCode(expectedStatusCodes: Set<Int> = DEFAULT_EXPECTED_STATUS_CODES) {
val status = statusCode()
if (status !in expectedStatusCodes) {
throw IOException("Unexpected status code: $status")
}
}

@ -0,0 +1,39 @@
package org.openrs2.http
import com.google.common.base.StandardSystemProperty
import java.io.IOException
import java.net.Authenticator
import java.net.PasswordAuthentication
import java.nio.file.Files
import java.nio.file.Paths
import java.util.Scanner
public class NetrcAuthenticator(
private val hosts: Map<String, PasswordAuthentication>,
private val default: PasswordAuthentication?
) : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication? {
return hosts.getOrDefault(requestingHost, default)
}
public companion object {
private val EMPTY_AUTHENTICATOR = NetrcAuthenticator(emptyMap(), null)
public fun read(): NetrcAuthenticator {
val home = StandardSystemProperty.USER_HOME.value() ?: throw IOException("user.home not defined")
val path = Paths.get(home, ".netrc")
return if (Files.exists(path)) {
Scanner(path, Charsets.UTF_8).use { scanner ->
read(scanner)
}
} else {
EMPTY_AUTHENTICATOR
}
}
public fun read(scanner: Scanner): NetrcAuthenticator {
return NetrcReader(scanner).read()
}
}
}

@ -0,0 +1,95 @@
package org.openrs2.http
import java.io.IOException
import java.net.PasswordAuthentication
import java.util.Scanner
import java.util.regex.Pattern
public class NetrcReader(private val scanner: Scanner) {
private enum class State {
DEFAULT,
READ_MACHINE
}
private val hosts = mutableMapOf<String, PasswordAuthentication>()
private var default: PasswordAuthentication? = null
private var host: String? = null
private var username: String? = null
private var password: String? = null
private var state = State.DEFAULT
set(value) {
if (field != State.DEFAULT) {
val auth = PasswordAuthentication(username ?: "", password?.toCharArray() ?: EMPTY_CHAR_ARRAY)
val host = host
if (host != null) {
hosts[host] = auth
} else if (default == null) {
default = auth
}
}
field = value
}
init {
scanner.useDelimiter(WHITESPACE)
}
public fun read(): NetrcAuthenticator {
while (scanner.hasNext()) {
when (val token = scanner.next()) {
"machine" -> {
if (!scanner.hasNext()) {
throw IOException("Expected hostname")
}
state = State.READ_MACHINE
host = scanner.next()
}
"default" -> {
state = State.READ_MACHINE
host = null
}
"login", "password", "account" -> {
if (state != State.READ_MACHINE) {
throw IOException("Unexpected token '$token'")
} else if (!scanner.hasNext()) {
throw IOException("Expected $token")
}
if (token == "login") {
username = scanner.next()
} else if (token == "password") {
password = scanner.next()
}
}
"macdef" -> skipMacro()
}
}
// trigger the logic to add the final machine to the map
state = State.DEFAULT
return NetrcAuthenticator(hosts, default)
}
private fun skipMacro() {
if (!scanner.hasNext()) {
throw IOException("Expected macro name")
}
while (scanner.hasNextLine() && scanner.nextLine().isNotEmpty()) {
// empty
}
state = State.DEFAULT
}
private companion object {
private val WHITESPACE = Pattern.compile("\\s+")
private val EMPTY_CHAR_ARRAY = charArrayOf()
}
}

@ -31,6 +31,7 @@ include(
"deob-processor",
"deob-util",
"game",
"http",
"json",
"net",
"nonfree",

Loading…
Cancel
Save