WebCORSHandler.swift 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import NIO
  2. import NIOHTTP1
  3. /// Handler that manages the CORS protocol for requests incoming from the browser.
  4. public class WebCORSHandler {
  5. var requestMethod: HTTPMethod?
  6. }
  7. extension WebCORSHandler: ChannelInboundHandler {
  8. public typealias InboundIn = HTTPServerRequestPart
  9. public typealias OutboundOut = HTTPServerResponsePart
  10. public func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
  11. // If the request is OPTIONS, the request is not propagated further.
  12. switch self.unwrapInboundIn(data) {
  13. case .head(let requestHead):
  14. requestMethod = requestHead.method
  15. if requestMethod == .OPTIONS {
  16. var headers = HTTPHeaders()
  17. headers.add(name: "Access-Control-Allow-Origin", value: "*")
  18. headers.add(name: "Access-Control-Allow-Methods", value: "POST")
  19. headers.add(name: "Access-Control-Allow-Headers",
  20. value: "content-type,x-grpc-web,x-user-agent")
  21. headers.add(name: "Access-Control-Max-Age", value: "86400")
  22. ctx.write(self.wrapOutboundOut(.head(HTTPResponseHead(version: requestHead.version,
  23. status: .ok,
  24. headers: headers))),
  25. promise: nil)
  26. return
  27. }
  28. case .body:
  29. if requestMethod == .OPTIONS {
  30. // OPTIONS requests do not have a body, but still handle this case to be
  31. // cautious.
  32. return
  33. }
  34. case .end:
  35. if requestMethod == .OPTIONS {
  36. ctx.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
  37. requestMethod = nil
  38. return
  39. }
  40. }
  41. // The OPTIONS request should be fully handled at this point.
  42. ctx.fireChannelRead(data)
  43. }
  44. }
  45. extension WebCORSHandler: ChannelOutboundHandler {
  46. public typealias OutboundIn = HTTPServerResponsePart
  47. public func write(ctx: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
  48. let responsePart = self.unwrapOutboundIn(data)
  49. switch responsePart {
  50. case .head(let responseHead):
  51. var headers = responseHead.headers
  52. // CORS requires all requests to have an Allow-Origin header.
  53. headers.add(name: "Access-Control-Allow-Origin", value: "*")
  54. //! FIXME: Check whether we can let browsers keep connections alive. It's not possible
  55. // now as the channel has a state that can't be reused since the pipeline is modified to
  56. // inject the gRPC call handler.
  57. headers.add(name: "Connection", value: "close")
  58. ctx.write(self.wrapOutboundOut(.head(HTTPResponseHead(version: responseHead.version,
  59. status: responseHead.status,
  60. headers: headers))),
  61. promise: promise)
  62. default:
  63. ctx.write(data, promise: promise)
  64. }
  65. }
  66. }