Swift

Swift HTTP Types 찍먹하기

GREEN.1229 2023. 7. 31. 10:14

안녕하세요. 그린입니다 🍏

이번 포스팅에서는 얼마전 공개된 Swift HTTP Types라는것에 대해 찍먹을 해보려해요.

 

최근 애플에서 Swift HTTP Types라는 새로운 오픈 소스 패키지를 만들었고 현재는 초기 단계이기에 활발히 진행되고 있습니다.

 

현재 릴리즈된 가장 최신 버전은 0.2.0이고 아직 메이저한 1.0.0 버전이 안나왔죠ㅎㅎ

 

그렇기에 현재 iOS에서도 기본 제공되는 프레임워크에 아직 속하지 않습니다🥲

조만간 Foundation 프레임워크에 같이 들어가지 않을까 추측해봐요!

 

현재는 찍먹해보고 싶으시다면 SPM으로 사용해야합니다!

 

GitHub - apple/swift-http-types: Version-independent HTTP currency types for Swift

Version-independent HTTP currency types for Swift. Contribute to apple/swift-http-types development by creating an account on GitHub.

github.com

 

그렇기에 정말 현재 아주 극 초기 단계처럼 보이기에 찍먹 느낌으로 가볼께요 🙌

 

먼저 Swift에서 HTTP를 인지하고 가볼께요.

 


HTTP in Swift

오늘날 Swift 네트워킹은 가장 많이 사용되는 네트워킹 기술 중 하나고 이미 전 세계에서 자연스럽고 당연하게 사용되고 있습니다.

 

애플 플랫폼에서 시스템의 HTTP 구현은 Foundation 프레임워크 내의 URLSession API를 통해 제공되죠.

서버에서 사용되는 Swift 프로젝트의 경우, SwiftNIO에서 구현된 HTTP 스택을 사용하는것이 권장됩니다.

Swift에서 HTTP를 정말 최상으로 사용하고 싶다면 여러 프로젝트에서 유용하게 사용할 수 있는 공통 타입들이 반드시 필요합니다.

 

자 그렇기에 여기서 유용하고 더 쉽게 사용하기 위해 해당 타입을 발전시켜 만든것 같아요.
이제부터 Swift HTTP Types에 대해 알아볼께요!

 


Swift HTTP Types

Swift HTTP Types는 Swift 서버와 앱 개발자들 그리고 더 큰 Swift 커뮤니티의 경험을 적용해 Swift에서 클라와 서버 간 HTTP 작업을 위한 공통 형태의 자료형들을 제공할 수 있도록 설계되었습니다.

 

즉, 그동안의 노하우들을 바탕으로 보다 더 편리하고 쉽게 설계되었겠죠 아무래도!? 😃

 

Swift HTTP Types는 HTTP 메시지의 기본 구성 요소를 공통적으로 나타냅니다.

HTTPReqeust와 HTTPResponse는 클라와 서버 모두를 위한 HTTP 메시지를 표현하죠.

여러 프로젝트에서 이를 적용함으로써, 클라와 서버 간의 더 많은 코드 공유를 할 수 있고 여러 프레임워크를 사용하면서 발생하는 타입 변환 비용을 줄일 수 있습니다.

 

이런 타입은 유효한 모든 HTTP 메시지를 표현하기 위해 Swift 중심으로 개발되었습니다.

최신 HTTP 버전인 HTTP/3 및 HTTP/2에 초점을 맞추고 있지만 HTTP/1.1과도 호환성을 유지해줍니다.

 

패키지가 더 발전되면서 SwiftNIO의 HTTPRequestHead 및 HTTPResponseHead와 함께 Foundation의 URLRequest와 URLResponse의 HTTP 메시지 세부 정보를 대체하는것이 목표라고해요!

 

곧 메이저하게 올라가면 기본 네트워킹 방식이 좀 더 편리해지고 현재 편리하게 사용하고 있는 Alamofire, Moya, Get과 같은 외부 라이브러리들의 사용이 최소화될 수 있겠네요 😉

 

결국 정리해보면, 새로운 공통 타입들은 기존 프레임워크에 종속되지 않고, 중복된 HTTP 추상화에 대한 필요성을 없애면서 모든 HTTP 환경에서 사용하기에 적합하도록 설계되었습니다.

 

그럼 이걸 어떻게 간단하게 사용할까요?
공식문서의 예시를 보면서 같이 확인해보시죠!

 


Swift HTTP Types 사용하기

우선, 사용되는 API는 일반적인 HTTP 작업들을 편리하게 처리하도록 설계되었으며, 동시에 고급 사용을 나타내기 위해 깔끔하게 처리할 수 있도록 확장성까지 갖추고 있습니다.

 

가장 핵심적으로 HTTP 요청은 메서드, 스킴, 권한 및 경로로 구성할 수 있습니다.

let request = HTTPRequest(
  method: .get, 
  scheme: "https", 
  authority: "www.example.com", 
  path: "/"
)

URL을 통해서도 같은 요청을 생성할 수도 있습니다.

var request = HTTPRequest(
  method: .get, 
  url: URL(string: "https://www.example.com/")!
)

 

생성이 된 후 메서드 혹은 기타 속성들을 변경할 수도 있습니다.

request.method = .post
request.path = "/upload"

 

Response 생성도 아주 간단하게 할 수 있죠.

let response = HTTPResponse(status: .ok)

 

또한 headerFields 프로퍼티를 사용해 헤더 필드에 접근하거나 변경할 수 있습니다.

request.headerFields[.userAgent] = "MyApp/1.0"

 

기본적으로 제공되는 헤더 필드 외에도, 사용자 정의 헤더 필드와 값에 대한 확장을 간편하게 추가할 수 있어, 비지니스 로직 구현 시에도 동일하게 편리한 코드를 사용할 수 있습니다.

extension HTTPField.Name {
  static let myCustomHeader = Self("My-Custom-Header")!
}

request.headerFields[.myCustomHeader] = "custom-value"

 

헤더 필드 값을 바로 변경할 수도 있고, 여러 개의 값을 배열 형태로 포함할 수도 있습니다.

request.headerFields[raw: .acceptLanguage] = ["en-US", "zh-Hans-CN"]

 

헤더 필드에 접근하는 방법도 매우 유사하게 작동하며, 시스템에서 정의된 필드 또는 사용자 커스텀 확장을 사용할 수도 있습니다.

request.headerFields[.userAgent] // "MyApp/1.0"
request.headerFields[.myCustomHeader] // "custom-value"
request.headerFields[.acceptLanguage] // "en-US, zh-Hans-CN"
request.headerFields[raw: .acceptLanguage] // ["en-US", "zh-Hans-CN"]

 

그럼 이렇게 만들어진 HTTPReqeust를 각 필요한 환경에서 어떻게 사용할 수 있을까요?

 


Foundation과 함께 사용

기존 사용하고 있던 Foundation의 URLSession을 활용하면 path로 네트워크 요청을 보내기 위한 새로운 HTTPRequest를 아주 쉽게 만들 수 있습니다.

 

해당 요청에 사용자 정의 User-Agent 헤더 필드 값을 설정하는것이 간편하며 자동 완성 기능을 지원하는 타입 시스템과 연동되어 작업이 편리해지죠.

var request = HTTPRequest(
  method: .post, 
  url: URL(string: "https://www.example.com/upload")!
)
request.headerFields[.userAgent] = "MyApp/1.0"

let (responseBody, response) = try await URLSession.shared.upload(for: request, from: requestBody)

guard response.status == .created else {
  // Handle error
}

 


SwiftNIO와 함께 사용

현재 Swift HTTP Types 패키지가 안정화되면 swift-nio-extras를 이용해 SwiftNIO와 연동할 수 있습니다.

새로운 HTTP 타입을 사용하는 NIO 채널 핸들러를 구성하기 위해, 기존 채널 핸들러 전에 HTTP2FramePayloadTOHTTPServerCodec을 추가하면 됩니다.

NIOTSListenerBootstrap(group: NIOTSEventLoopGroup())
  .childChannelInitializer { channel in
    channel.configureHTTP2Pipeline(mode: .server) { channel in
      channel.pipeline.addHandlers([
        HTTP2FramePayloadToHTTPServerCodec(),
        ExampleChannelHandler()
      ])
    }.map { _ in () }
  }
  .tlsOptions(tlsOptions)

 

샘플 채널 구현을 통해 HTTPReqeust와 HTTPResponse 타입이 양쪽 다 처리되는것을 확인할 수 있습니다.

final class ExampleChannelHandler: ChannelDuplexHandler {
  typealias InboundIn = HTTPTypeServerRequestPart
  typealias OutboundOut = HTTPTypeServerResponsePart
  
  func channelRead(context: ChannelHandlerContext, data: NIOAny) {
    switch unwrapInboundIn(data) {
    case .head(let request):
      // Handle request headers
    case .body(let body):
      // Handle request body
    case .end(let trailers):
      // Handle complete request
      let response = HTTPResponse(status: .ok)
      context.write(wrapOutboundOut(.head(response)), promise: nil)
      context.writeAndFlush(wrapOutboundOut(.end(nil)), promise: nil)
    }
  }
}

 


Request & Response body

그런데 여기까지 알아보는동안 Request와 Response의 body를 본적 없으시죠?

아쉽게도 현재는 해당 패키지에서는 HTTP Request와 Response의 body를 포함하고 있지 않아요.

기존의 Foundation에서 사용되는 Data와 InputStream, SwiftNIO에서 사용되는 ByteBuffer를 현재는 사용해야 합니다.

추후 body에 대해서도 계속 고안하고 더 나은 제안이 있다면 발전해나갈 계획이라고해요!

 


참고 레퍼런스

https://www.swift.org/blog/introducing-swift-http-types/

 

Introducing Swift HTTP Types

We’re excited to announce a new open source package called Swift HTTP Types.

www.swift.org