Add support for customizing HTTP headers (#1196)

This PR adds support for custom HTTP headers, introducing a
`--http-header` CLI flag to accept `key=value` pairs. These headers can
also be specified within the `setting.pkl` file.

Closes #633

SPICE: https://github.com/apple/pkl-evolution/pull/24

---------

Co-authored-by: Jen Basch <jbasch94@gmail.com>
Co-authored-by: Islon Scherer <islonscherer@gmail.com>
This commit is contained in:
Jeaeun Kim
2026-05-13 05:53:59 +09:00
committed by GitHub
parent fe58405220
commit 14085c18bb
14 changed files with 358 additions and 18 deletions
@@ -20,6 +20,7 @@ import java.nio.file.Files
import java.nio.file.Path
import java.time.Duration
import java.util.regex.Pattern
import org.pkl.core.Pair
import org.pkl.core.evaluatorSettings.Color
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings.ExternalReader
import org.pkl.core.evaluatorSettings.TraceMode
@@ -144,6 +145,9 @@ data class CliBaseOptions(
/** URL prefixes to rewrite. */
val httpRewrites: Map<URI, URI>? = null,
/** HTTP headers to add to the request. */
val httpHeaders: List<Pair<Pattern, List<Pair<String, String>>>>? = null,
/** External module reader process specs */
val externalModuleReaders: Map<String, ExternalReader> = mapOf(),
@@ -218,6 +218,10 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
cliOptions.httpRewrites ?: evaluatorSettings?.http?.rewrites ?: settings.http?.rewrites()
}
private val httpHeaders: List<Pair<Pattern, List<Pair<String, String>>>>? by lazy {
cliOptions.httpHeaders ?: project?.evaluatorSettings?.http?.headers ?: settings.http?.headers
}
protected val externalModuleReaders: Map<String, PklEvaluatorSettings.ExternalReader> by lazy {
(evaluatorSettings?.externalModuleReaders ?: emptyMap()) + cliOptions.externalModuleReaders
}
@@ -277,6 +281,7 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
setProxy(proxyAddress, noProxy ?: listOf())
}
httpRewrites?.let(::setRewrites)
httpHeaders?.let(::setHeaders)
// Lazy building significantly reduces execution time of commands that do minimal work.
// However, it means that HTTP client initialization errors won't surface until an HTTP
// request is made.
@@ -31,10 +31,12 @@ import java.util.regex.Pattern
import org.pkl.commons.cli.CliBaseOptions
import org.pkl.commons.cli.CliException
import org.pkl.commons.shlex
import org.pkl.core.Pair as PPair
import org.pkl.core.evaluatorSettings.Color
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings.ExternalReader
import org.pkl.core.evaluatorSettings.TraceMode
import org.pkl.core.runtime.VmUtils
import org.pkl.core.util.GlobResolver
import org.pkl.core.util.IoUtils
@Suppress("MemberVisibilityCanBePrivate")
@@ -285,6 +287,37 @@ class BaseOptions : OptionGroup() {
.multiple()
.toMap()
val httpHeaders: List<PPair<Pattern, List<PPair<String, String>>>> by
option(
names = arrayOf("--http-headers"),
metavar = "<url-pattern>=<header name>:<header value>",
help = "HTTP header to add to the request.",
)
.splitPair()
.transformAll { it ->
val headersMap = mutableMapOf<String, MutableList<PPair<String, String>>>()
try {
val headerRegex = Regex("""^(.+?):[ \t]*(.+)$""")
for ((stringPattern, header) in it) {
val (headerName, headerValue) =
headerRegex.find(header)?.destructured
?: fail("Header '$header' is not in 'name:value' format.")
IoUtils.validateHeaderName(headerName)
IoUtils.validateHeaderValue(headerValue)
headersMap
.computeIfAbsent(stringPattern) { mutableListOf() }
.add(PPair(headerName, headerValue))
}
headersMap.entries.map { PPair(GlobResolver.toRegexPattern(it.key), it.value) }
} catch (e: IllegalArgumentException) {
fail(e.message!!)
} catch (e: GlobResolver.InvalidGlobPatternException) {
fail(e.message!!)
}
}
val externalModuleReaders: Map<String, ExternalReader> by
option(
names = arrayOf("--external-module-reader"),
@@ -351,6 +384,7 @@ class BaseOptions : OptionGroup() {
httpProxy = proxy,
httpNoProxy = noProxy,
httpRewrites = httpRewrites.ifEmpty { null },
httpHeaders = httpHeaders.ifEmpty { null },
externalModuleReaders = externalModuleReaders,
externalResourceReaders = externalResourceReaders,
traceMode = traceMode,