287 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
		
		
			
		
	
	
			287 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
| 
								 | 
							
								/*  $Id$
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Part of SWI-Prolog
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Author:        Jan Wielemaker
							 | 
						||
| 
								 | 
							
								    E-mail:        wielemak@science.uva.nl
							 | 
						||
| 
								 | 
							
								    WWW:           http://www.swi-prolog.org
							 | 
						||
| 
								 | 
							
								    Copyright (C): 1985-2005, University of Amsterdam
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This program is free software; you can redistribute it and/or
							 | 
						||
| 
								 | 
							
								    modify it under the terms of the GNU General Public License
							 | 
						||
| 
								 | 
							
								    as published by the Free Software Foundation; either version 2
							 | 
						||
| 
								 | 
							
								    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 General Public License for more details.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    You should have received a copy of the GNU Lesser General Public
							 | 
						||
| 
								 | 
							
								    License along with this library; if not, write to the Free Software
							 | 
						||
| 
								 | 
							
								    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    As a special exception, if you link this library with other files,
							 | 
						||
| 
								 | 
							
								    compiled with a Free Software compiler, to produce an executable, this
							 | 
						||
| 
								 | 
							
								    library does not by itself cause the resulting executable to be covered
							 | 
						||
| 
								 | 
							
								    by the GNU General Public License. This exception does not however
							 | 
						||
| 
								 | 
							
								    invalidate any other reasons why the executable file might be covered by
							 | 
						||
| 
								 | 
							
								    the GNU General Public License.
							 | 
						||
| 
								 | 
							
								*/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- module(xpce_httpd,
							 | 
						||
| 
								 | 
							
									  [ http_current_server/2,	% ?:Goal, ?Port
							 | 
						||
| 
								 | 
							
									    http_server/2		% :Goal, :Options
							 | 
						||
| 
								 | 
							
									  ]).
							 | 
						||
| 
								 | 
							
								:- use_module(library(pce)).
							 | 
						||
| 
								 | 
							
								:- use_module(http_header).
							 | 
						||
| 
								 | 
							
								:- use_module(library(debug)).
							 | 
						||
| 
								 | 
							
								:- use_module(http_wrapper).
							 | 
						||
| 
								 | 
							
								:- use_module(library(lists)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- meta_predicate
							 | 
						||
| 
								 | 
							
									http_server(:, ?),
							 | 
						||
| 
								 | 
							
									http_server(:, ?, +).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%	@http_servers: keep track of them and avoid the servers being
							 | 
						||
| 
								 | 
							
								%	garbage collected.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- pce_global(@http_servers, new(chain)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%:- debug(connection).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_current_server(Goal, Port) :-
							 | 
						||
| 
								 | 
							
									object(@open_sockets),
							 | 
						||
| 
								 | 
							
									chain_list(@open_sockets, Sockets),
							 | 
						||
| 
								 | 
							
									member(Socket, Sockets),
							 | 
						||
| 
								 | 
							
									send(Socket, instance_of, interactive_httpd),
							 | 
						||
| 
								 | 
							
									get(Socket, goal, Goal),
							 | 
						||
| 
								 | 
							
									get(Socket, address, Port).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	http_server(:Goal, +Options) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Start server at given or arbitrary port.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_server(Goal, Options) :-
							 | 
						||
| 
								 | 
							
									select(port(Port), Options, Options1), !,
							 | 
						||
| 
								 | 
							
									http_server(Goal, Port, Options1).
							 | 
						||
| 
								 | 
							
								http_server(_Goal, _Options) :-
							 | 
						||
| 
								 | 
							
									throw(error(existence_error(option, port), _)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_server(Goal, Port, _Options) :-
							 | 
						||
| 
								 | 
							
									strip_module(Goal, M, PlainGoal),
							 | 
						||
| 
								 | 
							
									(   var(Port)
							 | 
						||
| 
								 | 
							
									->  new(X, interactive_httpd(M:PlainGoal)),
							 | 
						||
| 
								 | 
							
									    get(X, address, Port)
							 | 
						||
| 
								 | 
							
									;   new(X, interactive_httpd(M:PlainGoal, Port))
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
							 | 
						||
| 
								 | 
							
								XPCE based socket handling for   generic HTTP interface infra-structure.
							 | 
						||
| 
								 | 
							
								This module acts as a replacement for inetd_httpd, which allows a Prolog
							 | 
						||
| 
								 | 
							
								process to acts as an inet-driven HTTP server.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Using this module the user can easily  debug HTTP connections or provide
							 | 
						||
| 
								 | 
							
								services while running the XPCE GUI.
							 | 
						||
| 
								 | 
							
								- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- pce_begin_class(interactive_httpd, socket,
							 | 
						||
| 
								 | 
							
										   "Prolog HTTP debugger").
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								variable(allowed_hosts,	chain*,	 both, "Chain of regex with acceptable peers").
							 | 
						||
| 
								 | 
							
								variable(goal,		prolog,	 get,  "Goal to use for processing").
							 | 
						||
| 
								 | 
							
								variable(out_stream,	prolog,	 get,  "Stream for output").
							 | 
						||
| 
								 | 
							
								variable(peer,		name,	 get,  "Peer connection (host only)").
							 | 
						||
| 
								 | 
							
								variable(request,	string*, get,  "Data for first line").
							 | 
						||
| 
								 | 
							
								variable(data,		string*, get,  "Data for POST request").
							 | 
						||
| 
								 | 
							
								variable(chunk_data,	string*, get,  "Collect chunked input").
							 | 
						||
| 
								 | 
							
								variable(mode,
							 | 
						||
| 
								 | 
							
									 {request,header,post_content_length,chunked} := request,
							 | 
						||
| 
								 | 
							
												 get,  "Mode of operation").
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- pce_global(@http_end_header_regex,
							 | 
						||
| 
								 | 
							
									      new(regex('\n\r?\n\r?'))).
							 | 
						||
| 
								 | 
							
								:- pce_global(@http_end_line_regex,
							 | 
						||
| 
								 | 
							
									      new(regex('\n\r?'))).
							 | 
						||
| 
								 | 
							
								:- pce_global(@http_has_header_regex,
							 | 
						||
| 
								 | 
							
									      new(regex('[^\n]*HTTP/'))).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								initialise(S, Goal:prolog, Port:[int]) :->
							 | 
						||
| 
								 | 
							
									default(Port, 0, ThePort),	% anonymous
							 | 
						||
| 
								 | 
							
									send_super(S, initialise, ThePort),
							 | 
						||
| 
								 | 
							
									send(S, slot, goal, Goal),
							 | 
						||
| 
								 | 
							
									send(S, record_separator, @http_end_line_regex),
							 | 
						||
| 
								 | 
							
									send(S, input_message, message(@receiver, input, @arg1)),
							 | 
						||
| 
								 | 
							
									send(S, accept_message, message(@arg1, accepted)),
							 | 
						||
| 
								 | 
							
									send(S, listen, reuse := @on),
							 | 
						||
| 
								 | 
							
									send(@http_servers, append, S).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								unlink(S) :->
							 | 
						||
| 
								 | 
							
									send(@http_servers, delete_all, S),
							 | 
						||
| 
								 | 
							
									send_super(S, unlink).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- pce_group(connection).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								accepted(S) :->
							 | 
						||
| 
								 | 
							
									"A new connection is established on this socket"::
							 | 
						||
| 
								 | 
							
									(   pce_catch_error(_, get(S, peer_name, tuple(Peer, _Port)))
							 | 
						||
| 
								 | 
							
									->  send(S, slot, peer, Peer),
							 | 
						||
| 
								 | 
							
									    send(S, authorise),
							 | 
						||
| 
								 | 
							
									    debug(connection, 'New connection from ~w', [Peer]),
							 | 
						||
| 
								 | 
							
									    pce_open(S, append, Fd),
							 | 
						||
| 
								 | 
							
									    send(S, slot, out_stream, Fd)
							 | 
						||
| 
								 | 
							
									;   debug(connection, 'Cannot get peer: closing.', []),
							 | 
						||
| 
								 | 
							
									    free(S)
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								authorise(S) :->
							 | 
						||
| 
								 | 
							
									"See whether we will proceeed with this connection"::
							 | 
						||
| 
								 | 
							
									get(S, allowed_hosts, Allowed),
							 | 
						||
| 
								 | 
							
									(   Allowed == @nil
							 | 
						||
| 
								 | 
							
									->  true
							 | 
						||
| 
								 | 
							
									;   get(S, peer, Peer),
							 | 
						||
| 
								 | 
							
									    get(Allowed, find,
							 | 
						||
| 
								 | 
							
										message(@arg1, match, Peer),
							 | 
						||
| 
								 | 
							
										_)
							 | 
						||
| 
								 | 
							
									->  true
							 | 
						||
| 
								 | 
							
									;   debug(connection, 'Refused connection from ~w', [Peer]),
							 | 
						||
| 
								 | 
							
									    free(S),
							 | 
						||
| 
								 | 
							
									    fail
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								unlink(S) :->
							 | 
						||
| 
								 | 
							
									(   debugging(connection)
							 | 
						||
| 
								 | 
							
									->  get(S, peer, Peer),
							 | 
						||
| 
								 | 
							
									    debug(connection, 'Closed connection from ~w', [Peer])
							 | 
						||
| 
								 | 
							
									;   true
							 | 
						||
| 
								 | 
							
									),
							 | 
						||
| 
								 | 
							
									(   get(S, slot, out_stream, Fd),
							 | 
						||
| 
								 | 
							
									    Fd \== @nil
							 | 
						||
| 
								 | 
							
									->  catch(close(Fd), _, true)
							 | 
						||
| 
								 | 
							
									;   true
							 | 
						||
| 
								 | 
							
									),
							 | 
						||
| 
								 | 
							
									send_super(S, unlink).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- pce_group(request).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
							 | 
						||
| 
								 | 
							
								->input collects input from  the  stream   until  an  entire  request is
							 | 
						||
| 
								 | 
							
								complete. A request consists of one of the following:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									<Request>	::= <Action> <Path>\n
							 | 
						||
| 
								 | 
							
											  | <Action> <Path> HTTP/<Version>\n
							 | 
						||
| 
								 | 
							
											    <Header>
							 | 
						||
| 
								 | 
							
											    <Post Data>?
							 | 
						||
| 
								 | 
							
								- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								input(S, Input:string) :->
							 | 
						||
| 
								 | 
							
									"Process input.  The argument is the header"::
							 | 
						||
| 
								 | 
							
									get(S, mode, Mode),
							 | 
						||
| 
								 | 
							
									(   debugging(input)
							 | 
						||
| 
								 | 
							
									->  send(@pce, format, 'GOT (mode %s): "%s"\n', Mode, Input)
							 | 
						||
| 
								 | 
							
									;   true
							 | 
						||
| 
								 | 
							
									),
							 | 
						||
| 
								 | 
							
									(   Mode == request		% got first line
							 | 
						||
| 
								 | 
							
									->  (   send(@http_has_header_regex, match, Input)
							 | 
						||
| 
								 | 
							
									    ->	send(S, slot, request, Input),
							 | 
						||
| 
								 | 
							
										send(S, slot, mode, header),
							 | 
						||
| 
								 | 
							
										send(S, record_separator, @http_end_header_regex)
							 | 
						||
| 
								 | 
							
									    ;	send(S, dispatch, Input)
							 | 
						||
| 
								 | 
							
									    )
							 | 
						||
| 
								 | 
							
									;   Mode == header
							 | 
						||
| 
								 | 
							
									->  send(Input, prepend, S?request),
							 | 
						||
| 
								 | 
							
									    send(S, slot, request, @nil),
							 | 
						||
| 
								 | 
							
									    (	send(S, collect_post_data, Input)
							 | 
						||
| 
								 | 
							
									    ->	true
							 | 
						||
| 
								 | 
							
									    ;	send(S, dispatch, Input)
							 | 
						||
| 
								 | 
							
									    )
							 | 
						||
| 
								 | 
							
									;   Mode == post_content_length
							 | 
						||
| 
								 | 
							
									->  send(S, slot, mode, request),
							 | 
						||
| 
								 | 
							
									    send(S, record_separator, @http_end_line_regex),
							 | 
						||
| 
								 | 
							
									    get(S, data, Header),
							 | 
						||
| 
								 | 
							
									    send(Header, append, Input),
							 | 
						||
| 
								 | 
							
									    send(Header, lock_object, @on),
							 | 
						||
| 
								 | 
							
									    send(S, slot, data, @nil),
							 | 
						||
| 
								 | 
							
									    send(S, dispatch, Header),
							 | 
						||
| 
								 | 
							
									    send(Header, lock_object, @off)
							 | 
						||
| 
								 | 
							
									;   Mode == chunked
							 | 
						||
| 
								 | 
							
									->  get(S, chunk_data, ChunkData),
							 | 
						||
| 
								 | 
							
									    (   get(S, record_separator, Bytes),
							 | 
						||
| 
								 | 
							
									        integer(Bytes)
							 | 
						||
| 
								 | 
							
									    ->  send(ChunkData, append, Input),
							 | 
						||
| 
								 | 
							
										send(S, record_separator, '\n')
							 | 
						||
| 
								 | 
							
									    ;	send(Input, prepend, '0x'),
							 | 
						||
| 
								 | 
							
										get(Input, value, HexAtom),
							 | 
						||
| 
								 | 
							
										term_to_atom(Bytes, HexAtom),
							 | 
						||
| 
								 | 
							
										(   Bytes == 0
							 | 
						||
| 
								 | 
							
										->  get(S, data, Header),
							 | 
						||
| 
								 | 
							
										    get(ChunkData, size, ContentLength),
							 | 
						||
| 
								 | 
							
										    send(@http_chunked_regex, search, Header),
							 | 
						||
| 
								 | 
							
										    send(@http_chunked_regex, register_value, 0, Header,
							 | 
						||
| 
								 | 
							
											 string('Content-Length: %d', ContentLength)),
							 | 
						||
| 
								 | 
							
										    send(Header, append, ChunkData),
							 | 
						||
| 
								 | 
							
										    send(S, slot, chunk_data, @nil),
							 | 
						||
| 
								 | 
							
										    send(S, slot, mode, request),
							 | 
						||
| 
								 | 
							
										    send(S, record_separator, @http_end_line_regex),
							 | 
						||
| 
								 | 
							
										    send(S, dispatch, Header)
							 | 
						||
| 
								 | 
							
										;   send(S, record_separator, Bytes)
							 | 
						||
| 
								 | 
							
										)
							 | 
						||
| 
								 | 
							
									    )
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								dispatch(S, Input:string) :->
							 | 
						||
| 
								 | 
							
									"Hand complete input for dispatching"::
							 | 
						||
| 
								 | 
							
									(   debugging(dispatch)
							 | 
						||
| 
								 | 
							
									->  send(@pce, write_ln, Input)
							 | 
						||
| 
								 | 
							
									;   true
							 | 
						||
| 
								 | 
							
									),
							 | 
						||
| 
								 | 
							
									pce_open(Input, read, In),
							 | 
						||
| 
								 | 
							
									get(S, goal, Goal),
							 | 
						||
| 
								 | 
							
									get(S, out_stream, Out),
							 | 
						||
| 
								 | 
							
									(   catch(http_wrapper(Goal, In, Out, Close, []),
							 | 
						||
| 
								 | 
							
										  E, wrapper_error(E))
							 | 
						||
| 
								 | 
							
									->  close(In),
							 | 
						||
| 
								 | 
							
									    (   downcase_atom(Close, 'keep-alive')
							 | 
						||
| 
								 | 
							
									    ->  send(S, slot, mode, request), % prepare for next
							 | 
						||
| 
								 | 
							
										send(S, record_separator, @http_end_line_regex),
							 | 
						||
| 
								 | 
							
										send(S, slot, data, @nil)
							 | 
						||
| 
								 | 
							
									    ;   free(S)
							 | 
						||
| 
								 | 
							
									    )
							 | 
						||
| 
								 | 
							
									;   close(In),			% exception or failure
							 | 
						||
| 
								 | 
							
									    free(S)
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								wrapper_error(Error) :-
							 | 
						||
| 
								 | 
							
									(   debugging(connection)
							 | 
						||
| 
								 | 
							
									->  print_message(error, Error)
							 | 
						||
| 
								 | 
							
									;   true
							 | 
						||
| 
								 | 
							
									),
							 | 
						||
| 
								 | 
							
									fail.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- pce_group(post).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- pce_global(@http_content_length_regex,
							 | 
						||
| 
								 | 
							
									      new(regex('^Content-Length:[[:blank:]]*([0-9]+)', @off))).
							 | 
						||
| 
								 | 
							
								:- pce_global(@http_chunked_regex,
							 | 
						||
| 
								 | 
							
									      new(regex('^Transfer-encoding:[[:blank:]]*chunked', @off))).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								collect_post_data(S, Header:string) :->
							 | 
						||
| 
								 | 
							
									(   send(@http_content_length_regex, search, Header)
							 | 
						||
| 
								 | 
							
									->  get(@http_content_length_regex, register_value, Header,
							 | 
						||
| 
								 | 
							
										1, int, Len),
							 | 
						||
| 
								 | 
							
									    debug(dispatch, '[POST] Content-length: ~w~n', [Len]),
							 | 
						||
| 
								 | 
							
									    send(S, slot, mode, post_content_length),
							 | 
						||
| 
								 | 
							
									    send(S, slot, data, Header),
							 | 
						||
| 
								 | 
							
									    send(S, record_separator, Len)
							 | 
						||
| 
								 | 
							
									;   send(@http_chunked_regex, search, Header)
							 | 
						||
| 
								 | 
							
									->  send(S, slot, mode, chunked),
							 | 
						||
| 
								 | 
							
									    send(S, slot, chunk_data, new(string)),
							 | 
						||
| 
								 | 
							
									    send(S, record_separator, '\n')
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- pce_end_class(interactive_httpd).
							 | 
						||
| 
								 | 
							
								
							 |