Skip to content
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

Okcurl multiplatform build #7416

Merged
merged 7 commits into from
Aug 15, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 38 additions & 22 deletions okcurl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,62 @@ import com.vanniktech.maven.publish.KotlinJvm
import org.apache.tools.ant.taskdefs.condition.Os

plugins {
kotlin("jvm")
kotlin("multiplatform")
kotlin("kapt")
id("org.jetbrains.dokka")
id("com.vanniktech.maven.publish.base")
id("com.palantir.graal")
id("com.github.johnrengelman.shadow")
}

tasks.jar {
manifest {
attributes("Automatic-Module-Name" to "okhttp3.curl")
attributes("Main-Class" to "okhttp3.curl.Main")
kotlin {
jvm {
withJava()
}
}

// resources-templates.
sourceSets {
main {
resources.srcDirs("$buildDir/generated/resources-templates")
}
}
sourceSets {
all {
languageSettings.optIn("kotlin.RequiresOptIn")
}

dependencies {
api(projects.okhttp)
api(projects.loggingInterceptor)
implementation(libs.picocli)
implementation(libs.guava.jre)
val jvmMain by getting {
kotlin.srcDir("$buildDir/generated/resources-templates")

kapt(libs.picocli.compiler)
dependencies {
api(projects.okhttp)
api(projects.loggingInterceptor)
api(libs.squareup.okio)
implementation("com.github.ajalt.clikt:clikt:3.5.0")
api(libs.guava.jre)
}
}

testImplementation(projects.okhttpTestingSupport)
testImplementation(libs.junit.jupiter.api)
testImplementation(libs.assertj.core)
val jvmTest by getting {
dependencies {
dependsOn(jvmMain)
implementation(projects.okhttpTestingSupport)
api(libs.squareup.okio)
implementation(libs.kotlin.test.common)
implementation(libs.kotlin.test.annotations)
api(libs.assertk)
}
}
}
}

tasks.jar {
manifest {
attributes("Automatic-Module-Name" to "okhttp3.curl")
attributes("Main-Class" to "okhttp3.curl.Main")
}
}

tasks.shadowJar {
mergeServiceFiles()
}

graal {
mainClass("okhttp3.curl.Main")
mainClass("okhttp3.curl.MainKt")
outputName("okcurl")
graalVersion(libs.versions.graalvm.get())
javaVersion("11")
Expand All @@ -70,5 +85,6 @@ tasks.register<Copy>("copyResourcesTemplates") {
filteringCharset = Charsets.UTF_8.toString()
}.let {
tasks.processResources.dependsOn(it)
tasks.compileJava.dependsOn(it)
tasks["javaSourcesJar"].dependsOn(it)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
*/
package okhttp3.curl

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.UsageError
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.*
import com.github.ajalt.clikt.parameters.types.int
import java.io.IOException
import java.security.cert.X509Certificate
import java.util.Properties
import java.util.*
import java.util.concurrent.TimeUnit.SECONDS
import java.util.logging.ConsoleHandler
import java.util.logging.Level
import java.util.logging.LogRecord
import java.util.logging.Logger
import java.util.logging.SimpleFormatter
import java.util.logging.*
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManager
Expand All @@ -32,92 +33,52 @@ import kotlin.system.exitProcess
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.curl.Main.Companion.NAME
import okhttp3.curl.logging.LoggingUtil
import okhttp3.internal.format
import okhttp3.internal.http.StatusLine
import okhttp3.internal.http2.Http2
import okhttp3.internal.platform.Platform
import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.logging.LoggingEventListener
import okio.sink
import picocli.CommandLine
import picocli.CommandLine.Command
import picocli.CommandLine.IVersionProvider
import picocli.CommandLine.Option
import picocli.CommandLine.Parameters

@Command(name = NAME, description = ["A curl for the next-generation web."],
mixinStandardHelpOptions = true, versionProvider = Main.VersionProvider::class)
class Main : Runnable {
@Option(names = ["-X", "--request"], description = ["Specify request command to use"])
var method: String? = null
class Main : CliktCommand(name = NAME, help = "A curl for the next-generation web.") {
val method: String? by option("-X", "--request", help="Specify request command to use")

@Option(names = ["-d", "--data"], description = ["HTTP POST data"])
var data: String? = null
val data: String? by option("-d", "--data", help="HTTP POST data")

@Option(names = ["-H", "--header"], description = ["Custom header to pass to server"])
var headers: MutableList<String>? = null
val headers: List<String>? by option("-H", "--header", help="Custom header to pass to server").multiple()

@Option(names = ["-A", "--user-agent"], description = ["User-Agent to send to server"])
var userAgent = NAME + "/" + versionString()
val userAgent: String by option("-A", "--user-agent", help="User-Agent to send to server").default(NAME + "/" + versionString())

@Option(names = ["--connect-timeout"],
description = ["Maximum time allowed for connection (seconds)"])
var connectTimeout = DEFAULT_TIMEOUT
val connectTimeout: Int by option("--connect-timeout", help="Maximum time allowed for connection (seconds)").int().default(DEFAULT_TIMEOUT)

@Option(names = ["--read-timeout"],
description = ["Maximum time allowed for reading data (seconds)"])
var readTimeout = DEFAULT_TIMEOUT
val readTimeout: Int by option("--read-timeout", help="Maximum time allowed for reading data (seconds)").int().default(DEFAULT_TIMEOUT)

@Option(names = ["--call-timeout"],
description = ["Maximum time allowed for the entire call (seconds)"])
var callTimeout = DEFAULT_TIMEOUT
val callTimeout: Int by option("--call-timeout", help="Maximum time allowed for the entire call (seconds)").int().default(DEFAULT_TIMEOUT)

@Option(names = ["-L", "--location"], description = ["Follow redirects"])
var followRedirects: Boolean = false
val followRedirects: Boolean by option("-L", "--location", help="Follow redirects").flag()

@Option(names = ["-k", "--insecure"], description = ["Allow connections to SSL sites without certs"])
var allowInsecure: Boolean = false
val allowInsecure: Boolean by option("-k", "--insecure", help="Allow connections to SSL sites without certs").flag()

@Option(names = ["-i", "--include"], description = ["Include protocol headers in the output"])
var showHeaders: Boolean = false
val showHeaders: Boolean by option("-i", "--include", help="Include protocol headers in the output").flag()

@Option(names = ["--frames"], description = ["Log HTTP/2 frames to STDERR"])
var showHttp2Frames: Boolean = false
val showHttp2Frames: Boolean by option("--frames", help="Log HTTP/2 frames to STDERR").flag()

@Option(names = ["-e", "--referer"], description = ["Referer URL"])
var referer: String? = null
val referer: String? by option("-e", "--referer", help="Referer URL")

@Option(names = ["-v", "--verbose"], description = ["Makes $NAME verbose during the operation"])
var verbose: Boolean = false
val verbose: Boolean by option("-v", "--verbose", help="Makes $NAME verbose during the operation").flag()

@Option(names = ["--ssldebug"], description = ["Output SSL Debug"])
var sslDebug: Boolean = false
val sslDebug: Boolean by option(help="Output SSL Debug").flag()

@Option(names = ["--completionScript"], hidden = true)
var completionScript: Boolean = false

@Parameters(paramLabel = "url", description = ["Remote resource URL"])
var url: String? = null
val url: String? by argument(name = "url", help="Remote resource URL")

private lateinit var client: OkHttpClient

override fun run() {
if (completionScript) {
println(picocli.AutoComplete.bash("okcurl", CommandLine(Main())))
return
}

if (showHttp2Frames) {
enableHttp2FrameLogging()
}

if (sslDebug) {
enableSslDebugging()
}
LoggingUtil.configureLogging(debug = verbose, showHttp2Frames = showHttp2Frames, sslDebug = sslDebug)

client = createClient()
val request = createRequest()
Expand All @@ -135,13 +96,13 @@ class Main : Runnable {

// Stream the response to the System.out as it is returned from the server.
val out = System.out.sink()
val source = response.body!!.source()
val source = response.body.source()
while (!source.exhausted()) {
out.write(source.buffer, source.buffer.size)
out.flush()
}

response.body!!.close()
response.body.close()
} catch (e: IOException) {
e.printStackTrace()
} finally {
Expand Down Expand Up @@ -179,7 +140,7 @@ class Main : Runnable {

val requestMethod = method ?: if (data != null) "POST" else "GET"

val url = url ?: throw IllegalArgumentException("No url provided")
val url = url ?: throw UsageError("No url provided")

request.url(url)

Expand All @@ -189,7 +150,9 @@ class Main : Runnable {

for (header in headers.orEmpty()) {
val parts = header.split(':', limit = 2)
request.header(parts[0], parts[1])
if (!isSpecialHeader(parts[0])) {
request.header(parts[0], parts[1])
}
}
referer?.let {
request.header("Referer", it)
Expand All @@ -199,12 +162,15 @@ class Main : Runnable {
return request.build()
}

private fun isSpecialHeader(s: String): Boolean {
return s.equals("Content-Type", ignoreCase = true)
}

private fun mediaType(): MediaType? {
val mimeType = headers?.let {
for (header in it) {
val parts = header.split(':', limit = 2)
if ("Content-Type".equals(parts[0], ignoreCase = true)) {
it.remove(header)
return@let parts[1].trim()
}
}
Expand All @@ -219,34 +185,13 @@ class Main : Runnable {
client.dispatcher.executorService.shutdownNow()
}

class VersionProvider : IVersionProvider {
override fun getVersion(): Array<String> {
return arrayOf(
"$NAME ${versionString()}",
"Protocols: ${Protocol.values().joinToString(", ")}",
"Platform: ${Platform.get()::class.java.simpleName}"
)
}
}

companion object {
internal const val NAME = "okcurl"
internal const val DEFAULT_TIMEOUT = -1
private var frameLogger: Logger? = null
private var sslLogger: Logger? = null

@JvmStatic
fun main(args: Array<String>) {
if (System.getProperty("javax.net.debug") == null) {
System.setProperty("javax.net.debug", "")
}

exitProcess(CommandLine(Main()).execute(*args))
}

private fun versionString(): String? {
val prop = Properties()
Main::class.java.getResourceAsStream("/okcurl-version.properties").use {
Main::class.java.getResourceAsStream("/okcurl-version.properties")?.use {
prop.load(it)
}
return prop.getProperty("version", "dev")
Expand All @@ -267,38 +212,10 @@ class Main : Runnable {

private fun createInsecureHostnameVerifier(): HostnameVerifier =
HostnameVerifier { _, _ -> true }

private fun enableHttp2FrameLogging() {
frameLogger = Logger.getLogger(Http2::class.java.name).apply {
level = Level.FINE
addHandler(ConsoleHandler().apply {
level = Level.FINE
formatter = object : SimpleFormatter() {
override fun format(record: LogRecord): String {
return format("%s%n", record.message)
}
}
})
}
}

private fun enableSslDebugging() {
sslLogger = Logger.getLogger("javax.net.ssl").apply {
level = Level.FINE
addHandler(ConsoleHandler().apply {
level = Level.FINE
formatter = object : SimpleFormatter() {
override fun format(record: LogRecord): String {
val parameters = record.parameters
return if (parameters != null) {
format("%s%n%s%n", record.message, parameters.first())
} else {
format("%s%n", record.message)
}
}
}
})
}
}
}
}

fun main(args: Array<String>) {
Main().main(args)
exitProcess(0)
}