WebCORSHandler.swift 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  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 .head(let requestHead):
  29. requestMethod = requestHead.method
  30. if 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(name: "Access-Control-Allow-Headers",
  35. value: "content-type,x-grpc-web,x-user-agent")
  36. headers.add(name: "Access-Control-Max-Age", value: "86400")
  37. context.write(self.wrapOutboundOut(.head(HTTPResponseHead(version: requestHead.version,
  38. status: .ok,
  39. headers: headers))),
  40. promise: nil)
  41. return
  42. }
  43. case .body:
  44. if requestMethod == .OPTIONS {
  45. // OPTIONS requests do not have a body, but still handle this case to be
  46. // cautious.
  47. return
  48. }
  49. case .end:
  50. if requestMethod == .OPTIONS {
  51. context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
  52. requestMethod = nil
  53. return
  54. }
  55. }
  56. // The OPTIONS request should be fully handled at this point.
  57. context.fireChannelRead(data)
  58. }
  59. }
  60. extension WebCORSHandler: ChannelOutboundHandler {
  61. typealias OutboundIn = HTTPServerResponsePart
  62. func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
  63. let responsePart = self.unwrapOutboundIn(data)
  64. switch responsePart {
  65. case .head(let responseHead):
  66. var headers = responseHead.headers
  67. // CORS requires all requests to have an Allow-Origin header.
  68. headers.add(name: "Access-Control-Allow-Origin", value: "*")
  69. //! FIXME: Check whether we can let browsers keep connections alive. It's not possible
  70. // now as the channel has a state that can't be reused since the pipeline is modified to
  71. // inject the gRPC call handler.
  72. headers.add(name: "Connection", value: "close")
  73. context.write(self.wrapOutboundOut(.head(HTTPResponseHead(version: responseHead.version,
  74. status: responseHead.status,
  75. headers: headers))),
  76. promise: promise)
  77. default:
  78. context.write(data, promise: promise)
  79. }
  80. }
  81. }