PageRenderTime 39ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Suave/Proxy.fs

https://github.com/SuaveIO/suave
F# | 117 lines | 99 code | 18 blank | 0 comment | 11 complexity | 0ae81d2fffdba9849db17df5733382b8 MD5 | raw file
Possible License(s): Apache-2.0
  1. module Suave.Proxy
  2. open System
  3. open System.Net
  4. open Suave
  5. open Suave.Utils
  6. open Suave.Operators
  7. open Suave.Successful
  8. open Suave.Sockets
  9. open Suave.Sockets.Control
  10. let private (?) headers (name : string) =
  11. headers
  12. |> Seq.tryFind (fun (k, _) -> String.Equals(k, name, StringComparison.OrdinalIgnoreCase))
  13. |> Option.map snd
  14. let private httpWebResponseToHttpContext (ctx : HttpContext) (response : HttpWebResponse) =
  15. let status =
  16. match HttpCode.tryParse (int response.StatusCode) with
  17. | Choice1Of2 x -> x.status
  18. | _ -> HTTP_502.status
  19. let headers =
  20. response.Headers.AllKeys
  21. |> Seq.map (fun k -> k, response.Headers.Get k)
  22. |> Seq.toList
  23. let writeContentLengthHeader conn = socket {
  24. match headers ? ("Content-Length") with
  25. | Some x ->
  26. let! (_, conn) = asyncWriteLn (sprintf "Content-Length: %s" x) conn
  27. return conn
  28. | None ->
  29. return conn
  30. }
  31. let content =
  32. SocketTask
  33. (fun (conn, _) -> socket {
  34. let! conn = writeContentLengthHeader conn
  35. let! (_, conn) = asyncWriteLn "" conn
  36. let! conn = flush conn
  37. let stream = response.GetResponseStream ()
  38. do! transferStream conn stream
  39. return conn
  40. })
  41. {
  42. ctx with
  43. response =
  44. {
  45. ctx.response with
  46. status = status
  47. headers = headers
  48. content = content
  49. }
  50. }
  51. let proxy (newHost : Uri) : WebPart =
  52. (fun ctx ->
  53. async {
  54. let remappedAddress =
  55. if [ 80; 443 ] |> Seq.contains newHost.Port
  56. then
  57. sprintf "%s://%s%s" newHost.Scheme newHost.Host ctx.request.path
  58. else
  59. sprintf "%s://%s:%i%s" newHost.Scheme newHost.Host newHost.Port ctx.request.path
  60. let request = WebRequest.Create remappedAddress :?> HttpWebRequest
  61. request.Method <- ctx.request.rawMethod
  62. request.Proxy <- null
  63. request.AllowAutoRedirect <- false
  64. request.AllowReadStreamBuffering <- false
  65. request.AllowWriteStreamBuffering <- false
  66. match ctx.request.headers ? ("User-Agent") with | Some x -> request.UserAgent <- x | None -> ()
  67. match ctx.request.headers ? ("Accept") with | Some x -> request.Accept <- x | None -> ()
  68. match ctx.request.headers ? ("Date") |> Option.bind (Parse.dateTime >> Choice.toOption) with | Some x -> request.Date <- x | None -> ()
  69. match ctx.request.headers ? ("Host") with | Some x -> request.Host <- x | None -> ()
  70. match ctx.request.headers ? ("Content-Type") with | Some x -> request.ContentType <- x | None -> ()
  71. match ctx.request.headers ? ("Content-Length") |> Option.bind (Parse.int64 >> Choice.toOption) with | Some x -> request.ContentLength <- x | None -> ()
  72. request.Headers.Add("X-Forwarded-For", ctx.request.host)
  73. if [ HttpMethod.POST; HttpMethod.PUT ] |> Seq.contains ctx.request.method
  74. then
  75. let! requestStream =
  76. request.GetRequestStreamAsync ()
  77. |> Async.AwaitTask
  78. for b in ctx.request.rawForm do
  79. requestStream.WriteByte b
  80. try
  81. let! response = request.AsyncGetResponse ()
  82. let response = response :?> HttpWebResponse
  83. return httpWebResponseToHttpContext ctx response |> Some
  84. with
  85. | :? WebException as ex when not (isNull ex.Response) ->
  86. let response = ex.Response :?> HttpWebResponse
  87. return httpWebResponseToHttpContext ctx response |> Some
  88. | exn ->
  89. ctx.runtime.logger.log
  90. Logging.Error
  91. (fun lvl ->
  92. Logging.Message.event lvl (sprintf "Unable to proxy the request %A %A. " ctx.request.rawMethod remappedAddress)
  93. |> Logging.Message.addExn exn)
  94. return!
  95. (
  96. OK "Unable to proxy the request. "
  97. >=> Writers.setStatus HTTP_502
  98. ) ctx
  99. })