WebCORSHandler.swift 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. /*
  2. * Copyright 2019, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import NIO
  17. import NIOHTTP1
  18. /// Handler that manages the CORS protocol for requests incoming from the browser.
  19. internal class WebCORSHandler {
  20. var requestMethod: HTTPMethod?
  21. }
  22. extension WebCORSHandler: ChannelInboundHandler {
  23. typealias InboundIn = HTTPServerRequestPart
  24. typealias OutboundOut = HTTPServerResponsePart
  25. func channelRead(context: ChannelHandlerContext, data: NIOAny) {
  26. // If the request is OPTIONS, the request is not propagated further.
  27. switch self.unwrapInboundIn(data) {
  28. case let .head(requestHead):
  29. self.requestMethod = requestHead.method
  30. if self.requestMethod == .OPTIONS {
  31. var headers = HTTPHeaders()
  32. headers.add(name: "Access-Control-Allow-Origin", value: "*")
  33. headers.add(name: "Access-Control-Allow-Methods", value: "POST")
  34. headers.add(
  35. name: "Access-Control-Allow-Headers",
  36. value: "content-type,x-grpc-web,x-user-agent"
  37. )
  38. headers.add(name: "Access-Control-Max-Age", value: "86400")
  39. context.write(
  40. self.wrapOutboundOut(.head(HTTPResponseHead(
  41. version: requestHead.version,
  42. status: .ok,
  43. headers: headers
  44. ))),
  45. promise: nil
  46. )
  47. return
  48. }
  49. case .body:
  50. if self.requestMethod == .OPTIONS {
  51. // OPTIONS requests do not have a body, but still handle this case to be
  52. // cautious.
  53. return
  54. }
  55. case .end:
  56. if self.requestMethod == .OPTIONS {
  57. context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
  58. self.requestMethod = nil
  59. return
  60. }
  61. }
  62. // The OPTIONS request should be fully handled at this point.
  63. context.fireChannelRead(data)
  64. }
  65. }
  66. extension WebCORSHandler: ChannelOutboundHandler {
  67. typealias OutboundIn = HTTPServerResponsePart
  68. func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
  69. let responsePart = self.unwrapOutboundIn(data)
  70. switch responsePart {
  71. case let .head(responseHead):
  72. var headers = responseHead.headers
  73. // CORS requires all requests to have an Allow-Origin header.
  74. headers.add(name: "Access-Control-Allow-Origin", value: "*")
  75. //! FIXME: Check whether we can let browsers keep connections alive. It's not possible
  76. // now as the channel has a state that can't be reused since the pipeline is modified to
  77. // inject the gRPC call handler.
  78. headers.add(name: "Connection", value: "close")
  79. context.write(
  80. self.wrapOutboundOut(.head(HTTPResponseHead(
  81. version: responseHead.version,
  82. status: responseHead.status,
  83. headers: headers
  84. ))),
  85. promise: promise
  86. )
  87. default:
  88. context.write(data, promise: promise)
  89. }
  90. }
  91. }