Extension functions for HTTP messages and message builders providing convenient access to common HTTP headers with automatic parsing and type-safe handling for content types, charset, caching, cookies, and other HTTP properties.
Extension functions for building HTTP messages with convenient header setters and getters.
/**
* Set Content-Type header
* @param type the content type to set
*/
fun HttpMessageBuilder.contentType(type: ContentType): Unit
/**
* Get parsed Content-Type header
* @return ContentType if header exists and is valid, null otherwise
*/
fun HttpMessageBuilder.contentType(): ContentType?
/**
* Get charset from Content-Type header
* @return Charset if specified in content type, null otherwise
*/
fun HttpMessageBuilder.charset(): Charset?
/**
* Get ETag header value
* @return ETag value if present, null otherwise
*/
fun HttpMessageBuilder.etag(): String?
/**
* Get Vary header values
* @return list of header names from Vary header, null if not present
*/
fun HttpMessageBuilder.vary(): List<String>?
/**
* Get Content-Length header value
* @return content length if valid, null otherwise
*/
fun HttpMessageBuilder.contentLength(): Long?
/**
* Parse Set-Cookie headers into Cookie objects
* @return list of parsed cookies
*/
fun HttpMessageBuilder.cookies(): List<Cookie>Usage Examples:
import io.ktor.http.*
// Setting content type in message builder
fun buildHttpMessage(): HttpMessage {
return HttpMessageBuilder().apply {
contentType(ContentType.Application.Json)
userAgent("MyApp/1.0")
maxAge(3600)
ifNoneMatch("\"etag-123\"")
// Access parsed values
val type = contentType() // ContentType.Application.Json
val charset = charset() // null (no charset specified)
val length = contentLength() // null (not set)
}.build()
}
// Working with cookies
fun setCookiesInBuilder(builder: HttpMessageBuilder) {
builder.headers.append(HttpHeaders.SetCookie, "sessionId=abc123; Path=/; HttpOnly")
builder.headers.append(HttpHeaders.SetCookie, "theme=dark; Max-Age=3600")
val cookies = builder.cookies()
println(cookies.size) // 2
println(cookies[0].name) // "sessionId"
println(cookies[0].value) // "abc123"
println(cookies[0].httpOnly) // true
}Extension functions for reading HTTP message properties with automatic parsing.
/**
* Get parsed Content-Type header from HTTP message
* @return ContentType if header exists and is valid, null otherwise
*/
fun HttpMessage.contentType(): ContentType?
/**
* Get charset from Content-Type header in HTTP message
* @return Charset if specified in content type, null otherwise
*/
fun HttpMessage.charset(): Charset?
/**
* Get ETag header value from HTTP message
* @return ETag value if present, null otherwise
*/
fun HttpMessage.etag(): String?
/**
* Get Vary header values from HTTP message
* @return list of header names from Vary header, null if not present
*/
fun HttpMessage.vary(): List<String>?
/**
* Get Content-Length header value from HTTP message
* @return content length if valid, null otherwise
*/
fun HttpMessage.contentLength(): Long?
/**
* Parse Set-Cookie headers from HTTP message
* @return list of parsed Cookie objects
*/
fun HttpMessage.setCookie(): List<Cookie>
/**
* Parse Cache-Control header values
* @return list of HeaderValue objects representing cache control directives
*/
fun HttpMessage.cacheControl(): List<HeaderValue>Usage Examples:
import io.ktor.http.*
// Reading message properties
fun analyzeHttpMessage(message: HttpMessage) {
// Content analysis
val contentType = message.contentType()
println("Content type: ${contentType?.contentType}/${contentType?.contentSubtype}")
val charset = message.charset()
println("Charset: ${charset?.name}")
val contentLength = message.contentLength()
println("Content length: $contentLength bytes")
// ETag handling
val etag = message.etag()
if (etag != null) {
println("ETag: $etag")
val isWeak = etag.startsWith("W/")
val tagValue = etag.removePrefix("W/").trim('"')
println("Weak ETag: $isWeak, Value: $tagValue")
}
// Vary header analysis
val varyHeaders = message.vary()
if (!varyHeaders.isNullOrEmpty()) {
println("Varies by headers: ${varyHeaders.joinToString(", ")}")
}
// Cookie handling
val cookies = message.setCookie()
cookies.forEach { cookie ->
println("Cookie: ${cookie.name}=${cookie.value}")
println(" Domain: ${cookie.domain}")
println(" Path: ${cookie.path}")
println(" Secure: ${cookie.secure}")
println(" HttpOnly: ${cookie.httpOnly}")
if (cookie.maxAge > 0) {
println(" Max-Age: ${cookie.maxAge} seconds")
}
}
// Cache control analysis
val cacheDirectives = message.cacheControl()
cacheDirectives.forEach { directive ->
println("Cache directive: ${directive.value}")
directive.params.forEach { param ->
println(" ${param.name}: ${param.value}")
}
}
}Extension functions for setting common HTTP headers with validation.
/**
* Set Max-Age cache control directive
* @param seconds maximum age in seconds
*/
fun HttpMessageBuilder.maxAge(seconds: Int): Unit
/**
* Set If-None-Match header for conditional requests
* @param value ETag value to match against
*/
fun HttpMessageBuilder.ifNoneMatch(value: String): Unit
/**
* Set User-Agent header
* @param content user agent string
*/
fun HttpMessageBuilder.userAgent(content: String): UnitUsage Examples:
import io.ktor.http.*
// Building requests with convenience methods
fun buildApiRequest(): HttpMessage {
return HttpMessageBuilder().apply {
// Set common headers
contentType(ContentType.Application.Json.withCharset(Charsets.UTF_8))
userAgent("MyAPI-Client/2.1.0 (Android)")
// Conditional request
ifNoneMatch("\"v2.1-abc123\"")
// Caching
maxAge(300) // 5 minutes
// Verify settings
require(contentType()?.match(ContentType.Application.Json) == true)
require(charset() == Charsets.UTF_8)
require(etag() == null) // If-None-Match is for requests, ETag is for responses
}.build()
}
// Building responses with cache control
fun buildCachedResponse(data: String, etag: String): HttpMessage {
return HttpMessageBuilder().apply {
contentType(ContentType.Text.Plain.withCharset(Charsets.UTF_8))
headers.set(HttpHeaders.ETag, "\"$etag\"")
maxAge(3600) // 1 hour
headers.append(HttpHeaders.CacheControl, "public")
headers.set(HttpHeaders.Vary, "Accept-Encoding, Accept-Language")
// Add content
headers.set(HttpHeaders.ContentLength, data.toByteArray().size.toString())
// Verify response headers
val parsedEtag = etag()
val varyHeaders = vary()
val cacheControl = cacheControl()
println("ETag: $parsedEtag")
println("Varies by: ${varyHeaders?.joinToString(", ")}")
println("Cache directives: ${cacheControl.map { it.value }}")
}.build()
}import io.ktor.http.*
// Parse and analyze cookies from message
fun analyzeCookies(message: HttpMessage) {
val cookies = message.setCookie()
cookies.forEach { cookie ->
println("Cookie Analysis:")
println(" Name: ${cookie.name}")
println(" Value: ${cookie.value}")
println(" Encoding: ${cookie.encoding}")
// Security attributes
val securityLevel = when {
cookie.secure && cookie.httpOnly -> "High (Secure + HttpOnly)"
cookie.secure -> "Medium (Secure only)"
cookie.httpOnly -> "Medium (HttpOnly only)"
else -> "Low (No security flags)"
}
println(" Security: $securityLevel")
// Expiration analysis
when {
cookie.maxAge > 0 -> {
val hours = cookie.maxAge / 3600
val days = hours / 24
when {
days > 0 -> println(" Expires: in $days days")
hours > 0 -> println(" Expires: in $hours hours")
else -> println(" Expires: in ${cookie.maxAge} seconds")
}
}
cookie.expires != null -> {
println(" Expires: ${cookie.expires}")
}
else -> println(" Expires: Session cookie")
}
// Scope
println(" Domain: ${cookie.domain ?: "current domain"}")
println(" Path: ${cookie.path ?: "/"}")
// Extensions
if (cookie.extensions.isNotEmpty()) {
println(" Extensions:")
cookie.extensions.forEach { (key, value) ->
println(" $key${if (value != null) "=$value" else ""}")
}
}
}
}
// Content type handling with charset detection
fun handleContentType(message: HttpMessage): Pair<ContentType?, Charset?> {
val contentType = message.contentType()
val charset = message.charset()
// Handle different content types
when {
contentType?.match(ContentType.Application.Json) == true -> {
val effectiveCharset = charset ?: Charsets.UTF_8
println("JSON content with charset: ${effectiveCharset.name}")
return contentType to effectiveCharset
}
contentType?.match(ContentType.Text.Any) == true -> {
val effectiveCharset = charset ?: Charsets.UTF_8
println("Text content with charset: ${effectiveCharset.name}")
return contentType to effectiveCharset
}
contentType?.match(ContentType.Application.OctetStream) == true -> {
println("Binary content (no charset)")
return contentType to null
}
contentType?.match(ContentType.MultiPart.Any) == true -> {
println("Multipart content")
// Extract boundary parameter
val boundary = contentType.parameters.find { it.name == "boundary" }?.value
println("Boundary: $boundary")
return contentType to charset
}
else -> {
println("Unknown or no content type")
return null to null
}
}
}/**
* HTTP message interface for reading headers and properties
*/
interface HttpMessage {
val headers: Headers
}
/**
* HTTP message builder interface for constructing messages
*/
interface HttpMessageBuilder {
val headers: HeadersBuilder
}
/**
* Header value with parameters (used by cache control parsing)
*/
data class HeaderValue(
val value: String,
val params: List<HeaderValueParam> = emptyList()
)
/**
* Header value parameter
*/
data class HeaderValueParam(
val name: String,
val value: String
)