File: revproxy.ml

package info (click to toggle)
ocsigen 1.3.3-1squeeze1
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 3,488 kB
  • ctags: 4,784
  • sloc: ml: 35,847; makefile: 1,450; sh: 772; ansic: 29
file content (292 lines) | stat: -rw-r--r-- 10,258 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
(* Ocsigen
 * http://www.ocsigen.org
 * Module revproxy.ml
 * Copyright (C) 2007 Vincent Balat
 * Laboratoire PPS - CNRS Universit Paris Diderot
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, with linking exception;
 * either version 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *)

(** Reverse proxy for Ocsigen *)

(*
   The reverse proxy is still experimental because it relies on the
   experimental Ocsigen_http_client module.

   TODO
   - Change the policy for  trusted servers  for pipelining?
   (see ocsigen_http_client.ml)
   - add the ability to rewrite some headers from the config file
   (for ex after a redirection, the new URL is wrong)
   probably in another (filter) extension
   - enhance pipelining
   - HTTP/1.0
   - ...


   - Make possible to return for example (Ext_next 404) to allow
   other extensions to take the request?
   There is a problem if the body contains data (POST request) ...
   this data has been sent and is lost ...
*)

open Lwt
open Ocsigen_extensions
open Simplexmlparser

exception Bad_answer_from_http_server


(*****************************************************************************)
(* The table of redirections for each virtual server                         *)
type redir =
    { regexp: Netstring_pcre.regexp;
      full_url: Ocsigen_lib.yesnomaybe;
      dest: string;
      pipeline: bool;
      keephost: bool}


(*****************************************************************************)
(* Finding redirections *)


(** The function that will generate the pages from the request. *)

let gen dir = function
| Ocsigen_extensions.Req_found _ -> 
    Lwt.return Ocsigen_extensions.Ext_do_nothing
| Ocsigen_extensions.Req_not_found (err, ri) ->
  catch
    (* Is it a redirection? *)
    (fun () ->
       Ocsigen_messages.debug2 "--Revproxy: Is it a redirection?";
       let dest =
         let ri = ri.request_info in
         let fi full =
           Ocsigen_extensions.find_redirection
             dir.regexp
             full
             dir.dest
             ri.ri_ssl
             ri.ri_host
             ri.ri_server_port
             ri.ri_get_params_string
             ri.ri_sub_path_string
             ri.ri_full_path_string
         in 
         match dir.full_url with
           | Ocsigen_lib.Yes -> fi true
           | Ocsigen_lib.No -> fi false
           | Ocsigen_lib.Maybe -> 
               try fi false 
               with Ocsigen_extensions.Not_concerned -> fi true
       in
       let (https, host, port, uri) = 
         try
           match Ocsigen_lib.parse_url dest with
             | (Some https, Some host, port, uri, _, _, _, _) -> 
                 let port = match port with
                   | None -> if https then 443 else 80
                   | Some p -> p
                 in
                 (https, host, port, uri)
             | _ -> raise (Ocsigen_extensions.Error_in_config_file
                             ("Revproxy : error in destination URL "^dest))
(*VVV catch only Neturl exceptions! *)
         with e -> raise (Ocsigen_extensions.Error_in_config_file
                            ("Revproxy : error in destination URL "^dest^" - "^
                               Printexc.to_string e))
       in
       Ocsigen_messages.debug
         (fun () ->
            "--Revproxy: YES! Redirection to "^
              (if https then "https://" else "http://")^host^":"^
              (string_of_int port)^uri);

       Ocsigen_lib.get_inet_addr host >>= fun inet_addr ->

       (* It is now safe to start next request.
          We are sure that the request won't be taken in disorder.
          => We return.
       *)

       let host = 
         match
           if dir.keephost 
           then match ri.request_info.ri_host with 
             | Some h -> Some h
             | None -> None
           else None 
         with
           | Some h -> h
           | None -> 
               if (not https && port=80) || (https && port=443)
               then host
               else host^":"^string_of_int port
       in

       let do_request =
         let ri = ri.request_info in
         if dir.pipeline then
           Ocsigen_http_client.raw_request
             ~headers:ri.ri_http_frame.Ocsigen_http_frame.frame_header.Ocsigen_http_frame.Http_header.headers
             ~https
             ~port
             ~client:ri.ri_client
             ~keep_alive:true
             ~content:ri.ri_http_frame.Ocsigen_http_frame.frame_content
             ?content_length:ri.ri_content_length
             ~http_method:ri.ri_method
             ~host
             ~inet_addr
             ~uri ()
           else
             fun () ->
               Ocsigen_http_client.basic_raw_request
                 ~headers:ri.ri_http_frame.Ocsigen_http_frame.frame_header.Ocsigen_http_frame.Http_header.headers
                 ~https
                 ~port
                 ~content:ri.ri_http_frame.Ocsigen_http_frame.frame_content
                 ?content_length:ri.ri_content_length
                 ~http_method:ri.ri_method
                 ~host
                 ~inet_addr
                 ~uri ()
       in
       Lwt.return
         (Ext_found
            (fun () ->
               do_request ()

               >>= fun http_frame ->
               let headers =
                 http_frame.Ocsigen_http_frame.frame_header.Ocsigen_http_frame.Http_header.headers
               in
               let code =
                 match
                   http_frame.Ocsigen_http_frame.frame_header.Ocsigen_http_frame.Http_header.mode
                 with
                   | Ocsigen_http_frame.Http_header.Answer code -> code
                   | _ -> raise Bad_answer_from_http_server
               in
               match http_frame.Ocsigen_http_frame.frame_content with
                 | None ->
                     let empty_result = Ocsigen_http_frame.empty_result () in
                     let length =
                       Ocsigen_headers.get_content_length http_frame
                     in
                     Lwt.return
                       {empty_result with
                          Ocsigen_http_frame.res_content_length = length;
                          res_headers= headers;
                          res_stop_stream = http_frame.Ocsigen_http_frame.frame_abort;
                          res_code= code;
                       }
                 | Some stream ->
                     let default_result =
                       Ocsigen_http_frame.default_result ()
                     in
                     let length =
                       Ocsigen_headers.get_content_length http_frame
                     in
                     Lwt.return
                       {default_result with
                          Ocsigen_http_frame.res_content_length = length;
                          res_stream = (stream, None);
                          res_stop_stream =
                           http_frame.Ocsigen_http_frame.frame_abort;
                          res_headers= headers;
                          res_code= code;
                       }
            )
         )
    )
    (function
       | Not_concerned -> return (Ext_next err)
       | e -> fail e)




(*****************************************************************************)


let parse_config = function
  | Element ("revproxy", atts, []) ->
      let rec parse_attrs ((r, f, d, pipeline, h) as res) = function
        | [] -> res
        | ("regexp", regexp)::l when r = None -> (* deprecated *)
            parse_attrs
              (Some (Netstring_pcre.regexp ("^"^regexp^"$")), Ocsigen_lib.Maybe,
               d, pipeline, h)
              l
        | ("fullurl", regexp)::l when r = None ->
            parse_attrs
              (Some (Netstring_pcre.regexp ("^"^regexp^"$")), Ocsigen_lib.Yes,
               d, pipeline, h)
              l
        | ("suburl", regexp)::l when r = None ->
            parse_attrs
              (Some (Netstring_pcre.regexp ("^"^regexp^"$")), Ocsigen_lib.No,
               d, pipeline, h)
              l
        | ("dest", dest)::l when d = None ->
            parse_attrs
              (r, f, Some dest, pipeline, h)
              l
        | ("keephost", "keephost")::l ->
            parse_attrs
              (r, f, d, pipeline, true)
              l
        | ("nopipeline", "nopipeline")::l ->
            parse_attrs
              (r, f, d, false, h)
              l
        | (a, _) :: _ ->
            badconfig "Wrong or duplicate attribute '%s' for <revproxy>" a
      in
      let dir =
          match parse_attrs (None, Ocsigen_lib.Yes, None, true, false) atts with
          | (None, _, _, _, _) ->
              badconfig "Missing attribute 'regexp' for <revproxy>"
          | (_, _, None, _, _) ->
              badconfig "Missing attribute 'dest' for <revproxy>"
          | (Some r, full, Some d, pipeline, h) ->
              {
                regexp=r;
                full_url=full;
                dest=d;
                pipeline=pipeline;
                keephost=h;
             }
        in
        gen dir
  | Element (t, _, _) -> raise (Bad_config_tag_for_extension t)
  | _ -> raise (Error_in_config_file "(revproxy extension) Bad data")





(*****************************************************************************)
(** Registration of the extension *)
let () = register_extension
  ~name:"revproxy"
  ~fun_site:(fun _ _ _ _ -> parse_config)
  ~user_fun_site:(fun _ _ _ _ _ -> parse_config)
  ~respect_pipeline:true (* We ask ocsigen to respect pipeline order
                            when sending to extensions! *)
  ()