@ -0,0 +1,216 @@ | |||||
$Id: README,v 1.1 2005/05/05 22:28:27 chandrusf Exp $ | |||||
ibrowse is a HTTP client. The following are a list of features. | |||||
- RFC2616 compliant (AFAIK) | |||||
- supports GET, POST, OPTIONS and HEAD only | |||||
- Understands HTTP/0.9, HTTP/1.0 and HTTP/1.1 | |||||
- Understands chunked encoding | |||||
- Named pools of connections to each webserver | |||||
- Pipelining support | |||||
- Download to file | |||||
- Asynchronous requests. Responses are streamed to a process | |||||
- Basic authentication | |||||
- Supports proxy authentication | |||||
- Can talk to Secure webservers using SSL | |||||
- any other features in the code not listed here :) | |||||
Comments to : Chandrashekhar.Mullaparthi@t-mobile.co.uk | |||||
Here are some usage examples. Enjoy! | |||||
5> ibrowse:start(). | |||||
{ok,<0.94.0>} | |||||
%% A simple GET | |||||
6> ibrowse:send_req("http://intranet/messenger/", [], get). | |||||
{ok,"200", | |||||
[{"Server","Microsoft-IIS/5.0"}, | |||||
{"Content-Location","http://intranet/messenger/index.html"}, | |||||
{"Date","Fri, 17 Dec 2004 15:16:19 GMT"}, | |||||
{"Content-Type","text/html"}, | |||||
{"Accept-Ranges","bytes"}, | |||||
{"Last-Modified","Fri, 17 Dec 2004 08:38:21 GMT"}, | |||||
{"Etag","\"aa7c9dc313e4c41:d77\""}, | |||||
{"Content-Length","953"}], | |||||
"<html>\r\n\r\n<head>\r\n<title>Messenger</title>\r\n<meta name=\"GENERATOR\" content=\"Microsoft FrontPage 5.0\">\r\n<meta name=\"ProgId\" content=\"FrontPage.Editor.Document\">\r\n<meta name=\"description\" content=\"Messenger Home Page\">\r\n</head>\r\n\r\n<frameset border=\"0\" frameborder=\"0\" rows=\"60,*\">\r\n <frame src=\"/messenger/images/topnav.html\" name=\"mFrameTopNav\" scrolling=\"NO\" target=\"mFrameMain\">\r\n <frameset cols=\"18%,*\">\r\n <frameset rows=\"*,120\">\r\n <frame src=\"index-toc.html\" name=\"mFrameTOC\" target=\"mFrameMain\" scrolling=\"auto\" noresize=\"true\">\r\n <frame src=\"/shared/search/namesearch.html\" name=\"mFrameNameSearch\" scrolling=\"NO\" target=\"mFrameMain\">\r\n </frameset>\r\n <frame src=\"home/16-12-04-xmascardsmms.htm\" name=\"mFrameMain\" scrolling=\"auto\" target=\"mFrameMain\" id=\"mFrameMain\">\r\n </frameset>\r\n <noframes>\r\n <body>\r\n\r\n <p><i>This site requires a browser that can view frames.</i></p>\r\n\r\n </body>\r\n </noframes>\r\n</frameset>\r\n\r\n</html>"} | |||||
%% ============================================================================= | |||||
%% A GET using a proxy | |||||
7> ibrowse:send_req("http://www.google.com/", [], get, [], | |||||
[{proxy_user, "XXXXX"}, | |||||
{proxy_password, "XXXXX"}, | |||||
{proxy_host, "proxy"}, | |||||
{proxy_port, 8080}], 1000). | |||||
{ok,"302", | |||||
[{"Date","Fri, 17 Dec 2004 15:22:56 GMT"}, | |||||
{"Content-Length","217"}, | |||||
{"Content-Type","text/html"}, | |||||
{"Set-Cookie", | |||||
"PREF=ID=f58155c797f96096:CR=1:TM=1103296999:LM=1103296999:S=FiWdtAqQvhQ0TvHq; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com"}, | |||||
{"Server","GWS/2.1"}, | |||||
{"Location", | |||||
"http://www.google.co.uk/cxfer?c=PREF%3D:TM%3D1103296999:S%3Do8bEY2FIHwdyGenS&prev=/"}, | |||||
{"Via","1.1 netapp01 (NetCache NetApp/5.5R2)"}], | |||||
"<HTML><HEAD><TITLE>302 Moved</TITLE></HEAD><BODY>\n<H1>302 Moved</H1>\nThe document has moved\n<A HREF=\"http://www.google.co.uk/cxfer?c=PREF%3D:TM%3D1103296999:S%3Do8bEY2FIHwdyGenS&prev=/\">here</A>.\r\n</BODY></HTML>\r\n"} | |||||
%% ============================================================================= | |||||
%% A GET response saved to file. A temporary file is created and the | |||||
%% filename returned. The response will only be saved to file is the | |||||
%% status code is in the 200 range. The directory to download to can | |||||
%% be set using the application env var 'download_dir' - the default | |||||
%% is the current working directory. | |||||
8> ibrowse:send_req("http://www.erlang.se/", [], get, [], | |||||
[{proxy_user, "XXXXX"}, | |||||
{proxy_password, "XXXXX"}, | |||||
{proxy_host, "proxy"}, | |||||
{proxy_port, 8080}, | |||||
{save_response_to_file, true}], 1000). | |||||
{error,req_timedout} | |||||
%% ============================================================================= | |||||
9> ibrowse:send_req("http://www.erlang.se/", [], get, [], | |||||
[{proxy_user, "XXXXX"}, | |||||
{proxy_password, "XXXXX"}, | |||||
{proxy_host, "proxy"}, | |||||
{proxy_port, 8080}, | |||||
{save_response_to_file, true}], 5000). | |||||
{ok,"200", | |||||
[{"Transfer-Encoding","chunked"}, | |||||
{"Date","Fri, 17 Dec 2004 15:24:36 GMT"}, | |||||
{"Content-Type","text/html"}, | |||||
{"Server","Apache/1.3.9 (Unix)"}, | |||||
{"Via","1.1 netapp01 (NetCache NetApp/5.5R2)"}], | |||||
{file,"/Users/chandru/code/ibrowse/src/ibrowse_tmp_file_1103297041125854"}} | |||||
%% ============================================================================= | |||||
%% Setting size of connection pool and pipeline size. This sets the | |||||
%% number of maximum connections to this server to 10 and the pipeline | |||||
%% size to 1. Connections are setup a required. | |||||
11> ibrowse:set_dest("www.hotmail.com", 80, [{max_sessions, 10}, | |||||
{max_pipeline_size, 1}]). | |||||
ok | |||||
%% ============================================================================= | |||||
%% Example using the HEAD method | |||||
56> ibrowse:send_req("http://www.erlang.org", [], head). | |||||
{ok,"200", | |||||
[{"Date","Mon, 28 Feb 2005 04:40:53 GMT"}, | |||||
{"Server","Apache/1.3.9 (Unix)"}, | |||||
{"Last-Modified","Thu, 10 Feb 2005 09:31:23 GMT"}, | |||||
{"Etag","\"8d71d-1efa-420b29eb\""}, | |||||
{"Accept-ranges","bytes"}, | |||||
{"Content-Length","7930"}, | |||||
{"Content-Type","text/html"}], | |||||
[]} | |||||
%% ============================================================================= | |||||
%% Example using the OPTIONS method | |||||
62> ibrowse:send_req("http://www.sun.com", [], options). | |||||
{ok,"200", | |||||
[{"Server","Sun Java System Web Server 6.1"}, | |||||
{"Date","Mon, 28 Feb 2005 04:44:39 GMT"}, | |||||
{"Content-Length","0"}, | |||||
{"P3p", | |||||
"policyref=\"http://www.sun.com/p3p/Sun_P3P_Policy.xml\", CP=\"CAO DSP COR CUR ADMa DEVa TAIa PSAa PSDa CONi TELi OUR SAMi PUBi IND PHY ONL PUR COM NAV INT DEM CNT STA POL PRE GOV\""}, | |||||
{"Set-Cookie", | |||||
"SUN_ID=X.X.X.X:169191109565879; EXPIRES=Wednesday, 31-Dec-2025 23:59:59 GMT; DOMAIN=.sun.com; PATH=/"}, | |||||
{"Allow", | |||||
"HEAD, GET, PUT, POST, DELETE, TRACE, OPTIONS, MOVE, INDEX, MKDIR, RMDIR"}], | |||||
[]} | |||||
%% ============================================================================= | |||||
%% Example of using Asynchronous requests | |||||
18> ibrowse:send_req("http://www.google.com", [], get, [], | |||||
[{proxy_user, "XXXXX"}, | |||||
{proxy_password, "XXXXX"}, | |||||
{proxy_host, "proxy"}, | |||||
{proxy_port, 8080}, | |||||
{stream_to, self()}]). | |||||
{ibrowse_req_id,{1115,327256,389608}} | |||||
19> flush(). | |||||
Shell got {ibrowse_async_headers,{1115,327256,389608}, | |||||
"302", | |||||
[{"Date","Thu, 05 May 2005 21:06:41 GMT"}, | |||||
{"Content-Length","217"}, | |||||
{"Content-Type","text/html"}, | |||||
{"Set-Cookie", | |||||
"PREF=ID=b601f16bfa32f071:CR=1:TM=1115327201:LM=1115327201:S=OX5hSB525AMjUUu7; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com"}, | |||||
{"Server","GWS/2.1"}, | |||||
{"Location", | |||||
"http://www.google.co.uk/cxfer?c=PREF%3D:TM%3D1115327201:S%3DDS9pDJ4IHcAuZ_AS&prev=/"}, | |||||
{"Via", | |||||
"1.1 hatproxy01 (NetCache NetApp/5.6.2)"}]} | |||||
Shell got {ibrowse_async_response,{1115,327256,389608}, | |||||
"<HTML><HEAD><TITLE>302 Moved</TITLE></HEAD><BODY>\n<H1>302 Moved</H1>\nThe document has moved\n<A HREF=\"http://www.google.co.uk/cxfer?c=PREF%3D:TM%3D1115327201:S%3DDS9pDJ4IHcAuZ_AS&prev=/\">here</A>.\r\n</BODY></HTML>\r\n"} | |||||
Shell got {ibrowse_async_response_end,{1115,327256,389608}} | |||||
ok | |||||
%% ============================================================================= | |||||
%% Another example of using async requests | |||||
24> ibrowse:send_req("http://yaws.hyber.org/simple_ex2.yaws", [], get, [], | |||||
[{proxy_user, "XXXXX"}, | |||||
{proxy_password, "XXXXX"}, | |||||
{proxy_host, "proxy"}, | |||||
{proxy_port, 8080}, | |||||
{stream_to, self()}]). | |||||
{ibrowse_req_id,{1115,327430,512314}} | |||||
25> flush(). | |||||
Shell got {ibrowse_async_headers,{1115,327430,512314}, | |||||
"200", | |||||
[{"Date","Thu, 05 May 2005 20:58:08 GMT"}, | |||||
{"Content-Length","64"}, | |||||
{"Content-Type","text/html;charset="}, | |||||
{"Server", | |||||
"Yaws/1.54 Yet Another Web Server"}, | |||||
{"Via", | |||||
"1.1 hatproxy01 (NetCache NetApp/5.6.2)"}]} | |||||
Shell got {ibrowse_async_response,{1115,327430,512314}, | |||||
"<html>\n\n\n<h1> Yesssssss </h1>\n\n<h2> Hello again </h2>\n\n\n</html>\n"} | |||||
Shell got {ibrowse_async_response_end,{1115,327430,512314}} | |||||
%% ============================================================================= | |||||
%% Example of request which fails when using the async option. Here | |||||
%% the {ibrowse_req_id, ReqId} is not returned. Instead the error code is | |||||
%% returned. | |||||
68> ibrowse:send_req("http://www.earlyriser.org", [], get, [], [{stream_to, self()}]). | |||||
{error,conn_failed} | |||||
%% Example of request using both Proxy-Authorization and authorization by the final webserver. | |||||
17> ibrowse:send_req("http://www.erlang.se/lic_area/protected/patches/erl_756_otp_beam.README", | |||||
[], get, [], | |||||
[{proxy_user, "XXXXX"}, | |||||
{proxy_password, "XXXXX"}, | |||||
{proxy_host, "proxy"}, | |||||
{proxy_port, 8080}, | |||||
{basic_auth, {"XXXXX", "XXXXXX"}}]). | |||||
{ok,"200", | |||||
[{"Accept-Ranges","bytes"}, | |||||
{"Date","Thu, 05 May 2005 21:02:09 GMT"}, | |||||
{"Content-Length","2088"}, | |||||
{"Content-Type","text/plain"}, | |||||
{"Server","Apache/1.3.9 (Unix)"}, | |||||
{"Last-Modified","Tue, 03 May 2005 15:08:18 GMT"}, | |||||
{"ETag","\"1384c8-828-427793e2\""}, | |||||
{"Via","1.1 hatproxy01 (NetCache NetApp/5.6.2)"}], | |||||
"Patch Id:\t\terl_756_otp_beam\nLabel:\t\t\tinets patch\nDate:\t\t\t2005-05-03\nTrouble Report Id:\tOTP-5513, OTP-5514, OTP-5516, OTP-5517, OTP-5521, OTP-5537\nSeq num:\t\tseq9806\nSystem:\t\t\totp\nRelease:\t\tR10B\nOperating System:\tall\nArchitecture:\t\tall\nErlang machine:\t\tBEAM\nApplication:\t\tinets-4.4\nFiles:\t\t\tall\n\nDescription:\n\n OTP-5513 The server did not handle HTTP-0.9 messages with an implicit\n\t version.\n\n OTP-5514 An internal server timeout killed the request handling\n\t process without sending a message back to the client. As this\n\t timeout only affects a single request it has been set to\n\t infinity (if the main server process dies the request\n\t handling process will also die and the client will receive an\n\t error). This might make a client that does not use a timeout\n\t hang for a longer period of time, but that is an expected\n\t behavior!\n\n OTP-5516 That a third party closes the http servers accept socket is\n\t recoverable for inets, hence intes will only produce an info\n\t report as there was no error in inets but measures where\n\t taken to avoid failure due to errors elsewhere.\n\n OTP-5517 The HTTP client proxy settings where ignored. Bug introduced\n\t in inets-4.3.\n\n OTP-5521 Inets only sent the \"WWW-Authenticate\" header at the first\n\t attempt to get a page, if the user supplied the wrong\n\t user/password combination the header was not sent again. This\n\t forces the user to kill the browser entirely after a failed\n\t login attempt, before the user may try to login again. Inets\n\t now always send the authentication header.\n\n OTP-5537 A major rewrite of big parts of the HTTP server code was\n\t performed. There where many things that did not work\n\t satisfactory. Cgi script handling can never have worked\n\t properly and the cases when it did sort of work, a big\n\t unnecessary delay was enforced. Headers where not always\n\t treated as expected and HTTP version handling did not work,\n\t all responses where sent as version HTTP/1.1 no matter what.\n\n\n"} | |||||
%% ============================================================================= | |||||
%% Example of a TRACE request. Very interesting! yaws.hyber.org didn't | |||||
%% support this. Nor did www.google.com. But good old BBC supports | |||||
%% this. | |||||
35> 37> ibrowse:send_req("http://www.bbc.co.uk/", [], trace, [], | |||||
[{proxy_user, "XXXXX"}, | |||||
{proxy_password, "XXXXX"}, | |||||
{proxy_host, "proxy"}, | |||||
{proxy_port, 8080}]). | |||||
{ok,"200", | |||||
[{"Transfer-Encoding","chunked"}, | |||||
{"Date","Thu, 05 May 2005 21:40:27 GMT"}, | |||||
{"Content-Type","message/http"}, | |||||
{"Server","Apache/2.0.51 (Unix)"}, | |||||
{"Set-Cookie", | |||||
"BBC-UID=7452e72a29424c5b0b232c7131c7d9395d209b7170e8604072e0fcb3630467300; expires=Mon, 04-May-09 21:40:27 GMT; path=/; domain=bbc.co.uk;"}, | |||||
{"Set-Cookie", | |||||
"BBC-UID=7452e72a29424c5b0b232c7131c7d9395d209b7170e8604072e0fcb3630467300; expires=Mon, 04-May-09 21:40:27 GMT; path=/; domain=bbc.co.uk;"}, | |||||
{"Via","1.1 hatproxy01 (NetCache NetApp/5.6.2)"}], | |||||
"TRACE / HTTP/1.1\r\nHost: www.bbc.co.uk\r\nConnection: keep-alive\r\nX-Forwarded-For: 172.24.28.29\r\nVia: 1.1 hatproxy01 (NetCache NetApp/5.6.2)\r\nCookie: BBC-UID=7452e72a29424c5b0b232c7131c7d9395d209b7170e8604072e0fcb3630467300\r\n\r\n"} |
@ -0,0 +1 @@ | |||||
cc -o ../priv/ibrowse_drv.so -I ~/R9C-0/usr/include/ -bundle -flat_namespace -undefined suppress -fno-common ibrowse_drv.c |
@ -0,0 +1,162 @@ | |||||
/* Created 07/March/2004 Chandrashekhar Mullaparthi | |||||
$Id: ibrowse_drv.c,v 1.1 2005/05/05 22:28:28 chandrusf Exp $ | |||||
Erlang Linked in driver to URL encode a set of data | |||||
*/ | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include "erl_driver.h" | |||||
static ErlDrvData ibrowse_drv_start(ErlDrvPort port, char* buff); | |||||
static void ibrowse_drv_stop(ErlDrvData handle); | |||||
static void ibrowse_drv_command(ErlDrvData handle, char *buff, int bufflen); | |||||
static void ibrowse_drv_finish(void); | |||||
static int ibrowse_drv_control(ErlDrvData handle, unsigned int command, | |||||
char* buf, int count, char** res, int res_size); | |||||
/* The driver entry */ | |||||
static ErlDrvEntry ibrowse_driver_entry = { | |||||
NULL, /* init, N/A */ | |||||
ibrowse_drv_start, /* start, called when port is opened */ | |||||
ibrowse_drv_stop, /* stop, called when port is closed */ | |||||
NULL, /* output, called when erlang has sent */ | |||||
NULL, /* ready_input, called when input descriptor | |||||
ready */ | |||||
NULL, /* ready_output, called when output | |||||
descriptor ready */ | |||||
"ibrowse_drv", /* char *driver_name, the argument | |||||
to open_port */ | |||||
NULL, /* finish, called when unloaded */ | |||||
NULL, /* void * that is not used (BC) */ | |||||
ibrowse_drv_control, /* control, port_control callback */ | |||||
NULL, /* timeout, called on timeouts */ | |||||
NULL, /* outputv, vector output interface */ | |||||
NULL, | |||||
NULL, | |||||
NULL, /* call, synchronous call to driver */ | |||||
NULL | |||||
}; | |||||
typedef struct ibrowse_drv_data { | |||||
unsigned int count; | |||||
void *alloc_ptr; | |||||
} State; | |||||
static State *ibrowse_drv_data; | |||||
DRIVER_INIT(ibrowse_drv) | |||||
{ | |||||
ibrowse_drv_data = NULL; | |||||
return &ibrowse_driver_entry; | |||||
} | |||||
static ErlDrvData ibrowse_drv_start(ErlDrvPort port, char *buff) | |||||
{ | |||||
State *state; | |||||
state = driver_alloc(sizeof(State)); | |||||
state->count = 0; | |||||
state->alloc_ptr = NULL; | |||||
ibrowse_drv_data = state; | |||||
return ((ErlDrvData) state); | |||||
} | |||||
void ibrowse_drv_stop(ErlDrvData desc) | |||||
{ | |||||
return; | |||||
} | |||||
static int ibrowse_drv_control(ErlDrvData handle, unsigned int command, | |||||
char *buf, int bufflen, char **rbuf, int rlen) | |||||
{ | |||||
State* state = (State *) handle; | |||||
unsigned int j = 0, i = 0; | |||||
unsigned int temp = 0, rlen_1 = 0; | |||||
char* replybuf; | |||||
fprintf(stderr, "alloc_ptr -> %d\n", state->alloc_ptr); | |||||
/* if(state->alloc_ptr != NULL) */ | |||||
/* { */ | |||||
/* driver_free(state->alloc_ptr); */ | |||||
/* } */ | |||||
/* Calculate encoded length. If same as bufflen, it means there is | |||||
no encoding to do. Do return an empty list */ | |||||
rlen_1 = calc_encoded_length(buf, bufflen); | |||||
if(rlen_1 == bufflen) | |||||
{ | |||||
*rbuf = NULL; | |||||
state->alloc_ptr = NULL; | |||||
return 0; | |||||
} | |||||
*rbuf = driver_alloc(rlen_1); | |||||
state->alloc_ptr = *rbuf; | |||||
fprintf(stderr, "*rbuf -> %d\n", *rbuf); | |||||
replybuf = *rbuf; | |||||
for(i=0;i<bufflen;i++) | |||||
{ | |||||
temp = buf[i]; | |||||
if( 'a' <= temp && temp <= 'z' | |||||
|| 'A' <= temp && temp <= 'Z' | |||||
|| '0' <= temp && temp <= '9' | |||||
|| temp == '-' || temp == '_' || temp == '.' ) | |||||
{ | |||||
replybuf[j++] = temp; | |||||
/* printf("j -> %d\n", j); */ | |||||
} | |||||
else | |||||
{ | |||||
replybuf[j++] = 37; | |||||
/* printf("temp -> %d\n", temp); | |||||
printf("d2h(temp >> 4) -> %d\n", d2h(temp >> 4)); | |||||
printf("d2h(temp & 15) -> %d\n", d2h(temp & 15)); */ | |||||
replybuf[j++] = d2h(temp >> 4); | |||||
replybuf[j++] = d2h(temp & 15); | |||||
/* printf("j -> %d\n", j); */ | |||||
} | |||||
} | |||||
return rlen_1; | |||||
} | |||||
/* Calculates the length of the resulting buffer if a string is URL encoded */ | |||||
int calc_encoded_length(char* buf, int bufflen) | |||||
{ | |||||
unsigned int count=0, i=0, temp=0; | |||||
for(i=0;i<bufflen;i++) | |||||
{ | |||||
temp = buf[i]; | |||||
if( 'a' <= temp && temp <= 'z' | |||||
|| 'A' <= temp && temp <= 'Z' | |||||
|| '0' <= temp && temp <= '9' | |||||
|| temp == '-' || temp == '_' || temp == '.' ) | |||||
{ | |||||
count++; | |||||
} | |||||
else | |||||
{ | |||||
count = count+3; | |||||
} | |||||
} | |||||
return count; | |||||
} | |||||
/* Converts an integer in the range 1-15 into it's ascii value | |||||
1 -> 49 (ascii value of 1) | |||||
10 -> 97 (ascii value of a) | |||||
*/ | |||||
int d2h(unsigned int i) | |||||
{ | |||||
if( i < 10 ) | |||||
{ | |||||
return i + 48; | |||||
} | |||||
else | |||||
{ | |||||
return i + 97 - 10; | |||||
} | |||||
} |
@ -0,0 +1,225 @@ | |||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> | |||||
<html> | |||||
<head> | |||||
<title>Module ibrowse</title> | |||||
<link rel="stylesheet" type="text/css" href="stylesheet.css"> | |||||
</head> | |||||
<body bgcolor="white"> | |||||
<h1>Module ibrowse</h1> | |||||
<ul><li> | |||||
<a href="#index">Function index</a></li><li> | |||||
<a href="#exported">Exported functions</a></li></ul> | |||||
<h2>Description</h2> | |||||
The ibrowse application implements an HTTP 1.1 client. This | |||||
module implements the API of the HTTP client. There is one named | |||||
process called 'ibrowse' which acts as a load balancer. There is | |||||
one process to handle one TCP connection to a webserver | |||||
(implemented in the module ibrowse_http_client). Multiple connections to a | |||||
webserver are setup based on the settings for each webserver. The | |||||
ibrowse process also determines which connection to pipeline a | |||||
certain request on. The functions to call are send_req/3, | |||||
send_req/4, send_req/5, send_req/6. | |||||
<p>Here are a few sample invocations.</p> | |||||
<code> | |||||
ibrowse:send_req("http://intranet/messenger/", [], get). | |||||
<br><br> | |||||
ibrowse:send_req("http://www.google.com/", [], get, [], | |||||
[{proxy_user, "XXXXX"}, | |||||
{proxy_password, "XXXXX"}, | |||||
{proxy_host, "proxy"}, | |||||
{proxy_port, 8080}], 1000). | |||||
<br><br> | |||||
ibrowse:send_req("http://www.erlang.org/download/otp_src_R10B-3.tar.gz", [], get, [], | |||||
[{proxy_user, "XXXXX"}, | |||||
{proxy_password, "XXXXX"}, | |||||
{proxy_host, "proxy"}, | |||||
{proxy_port, 8080}, | |||||
{save_response_to_file, true}], 1000). | |||||
<br><br> | |||||
ibrowse:set_dest("www.hotmail.com", 80, [{max_sessions, 10}, | |||||
{max_pipeline_size, 1}]). | |||||
<br><br> | |||||
ibrowse:send_req("http://www.erlang.org", [], head). | |||||
<br><br> | |||||
ibrowse:send_req("http://www.sun.com", [], options). | |||||
<br><br> | |||||
ibrowse:send_req("http://www.bbc.co.uk", [], trace). | |||||
<br><br> | |||||
ibrowse:send_req("http://www.google.com", [], get, [], | |||||
[{stream_to, self()}]). | |||||
</code> | |||||
<p>A driver exists which implements URL encoding in C, but the | |||||
speed achieved using only erlang has been good enough, so the | |||||
driver isn't actually used.</p> | |||||
<h2><a name="index">Function Index</a></h2> | |||||
<table width="100%" border="1"><tr><th colspan="2" align="left">Exported Functions</th></tr> | |||||
<tr><td><a href="#code_change-3">code_change/3</a></td><td/></tr> | |||||
<tr><td><a href="#finished_async_request-0">finished_async_request/0</a></td><td>Internal export.</td></tr> | |||||
<tr><td><a href="#handle_call-3">handle_call/3</a></td><td/></tr> | |||||
<tr><td><a href="#handle_cast-2">handle_cast/2</a></td><td/></tr> | |||||
<tr><td><a href="#handle_info-2">handle_info/2</a></td><td/></tr> | |||||
<tr><td><a href="#init-1">init/1</a></td><td/></tr> | |||||
<tr><td><a href="#reply-2">reply/2</a></td><td>Internal export.</td></tr> | |||||
<tr><td><a href="#send_req-3">send_req/3</a></td><td>This is the basic function to send a HTTP request.</td></tr> | |||||
<tr><td><a href="#send_req-4">send_req/4</a></td><td>Same as send_req/3.</td></tr> | |||||
<tr><td><a href="#send_req-5">send_req/5</a></td><td>Same as send_req/4.</td></tr> | |||||
<tr><td><a href="#send_req-6">send_req/6</a></td><td>Same as send_req/5.</td></tr> | |||||
<tr><td><a href="#set_dest-3">set_dest/3</a></td><td>Sets options for a destination.</td></tr> | |||||
<tr><td><a href="#shutting_down-0">shutting_down/0</a></td><td>Internal export.</td></tr> | |||||
<tr><td><a href="#start-0">start/0</a></td><td/></tr> | |||||
<tr><td><a href="#start_link-0">start_link/0</a></td><td/></tr> | |||||
<tr><td><a href="#stop-0">stop/0</a></td><td/></tr> | |||||
<tr><td><a href="#terminate-2">terminate/2</a></td><td/></tr> | |||||
<tr><td><a href="#trace_off-0">trace_off/0</a></td><td>Turn tracing off for the ibrowse process.</td></tr> | |||||
<tr><td><a href="#trace_off-2">trace_off/2</a></td><td>Turn tracing OFF for all connections to the specified HTTP | |||||
server.</td></tr> | |||||
<tr><td><a href="#trace_on-0">trace_on/0</a></td><td>Turn tracing on for the ibrowse process.</td></tr> | |||||
<tr><td><a href="#trace_on-2">trace_on/2</a></td><td>Turn tracing on for all connections to the specified HTTP | |||||
server.</td></tr> | |||||
</table> | |||||
<h2><a name="exported">Exported Functions</a></h2> | |||||
<h3><a name="code_change-3">code_change/3</a></h3> | |||||
<p><code>code_change(Arg1, Arg2, Arg3) -> term()</code></p> | |||||
<p> </p> | |||||
<h3><a name="finished_async_request-0">finished_async_request/0</a></h3> | |||||
<p><code>finished_async_request() -> term()</code></p> | |||||
<p>Internal export. Called by a HTTP connection process to | |||||
indicate to the load balancing process (ibrowse) that an | |||||
asynchronous request has finished processing.</p> | |||||
<h3><a name="handle_call-3">handle_call/3</a></h3> | |||||
<p><code>handle_call(Arg1, Arg2, Arg3) -> term()</code></p> | |||||
<p> </p> | |||||
<h3><a name="handle_cast-2">handle_cast/2</a></h3> | |||||
<p><code>handle_cast(Arg1, Arg2) -> term()</code></p> | |||||
<p> </p> | |||||
<h3><a name="handle_info-2">handle_info/2</a></h3> | |||||
<p><code>handle_info(Arg1, Arg2) -> term()</code></p> | |||||
<p> </p> | |||||
<h3><a name="init-1">init/1</a></h3> | |||||
<p><code>init(Arg1) -> term()</code></p> | |||||
<p> </p> | |||||
<h3><a name="reply-2">reply/2</a></h3> | |||||
<p><code>reply(Arg1, Arg2) -> term()</code></p> | |||||
<p>Internal export. Called by a HTTP connection process to | |||||
indicate to the load balancing process (ibrowse) that a synchronous | |||||
request has finished processing.</p> | |||||
<h3><a name="send_req-3">send_req/3</a></h3> | |||||
<p><code>send_req(Url::string(), Headers::<a href="#type-headerList">headerList()</a>, Method::<a href="#type-method">method()</a>) -> <a href="#type-response">response()</a><ul><li><a name="type-headerList">headerList()</a> = [{<a href="#type-header">header()</a>, <a href="#type-value">value()</a>}]</li><li><a name="type-header">header()</a> = atom() | string()</li><li><a name="type-value">value()</a> = term()</li><li><a name="type-method">method()</a> = get | post | head | options | put | delete | trace</li><li>Status = string()</li><li>ResponseHeaders = [<a href="#type-respHeader">respHeader()</a>]</li><li><a name="type-respHeader">respHeader()</a> = {<a href="#type-headerName">headerName()</a>, <a href="#type-headerValue">headerValue()</a>}</li><li><a name="type-headerName">headerName()</a> = string()</li><li><a name="type-headerValue">headerValue()</a> = string()</li><li><a name="type-response">response()</a> = {ok, Status, ResponseHeaders, ResponseBody} | {error, Reason}</li><li>ResponseBody = string()</li><li>Reason = term()</li></ul></code></p> | |||||
<p>This is the basic function to send a HTTP request. | |||||
The Status return value indicates the HTTP status code returned by the webserver</p> | |||||
<h3><a name="send_req-4">send_req/4</a></h3> | |||||
<p><code>send_req(Url, Headers, Method::<a href="#type-method">method()</a>, Body::<a href="#type-body">body()</a>) -> <a href="#type-response">response()</a><ul><li><a name="type-body">body()</a> = [] | string() | binary()</li></ul></code></p> | |||||
<p>Same as send_req/3. | |||||
If a list is specified for the body it has to be a flat list.</p> | |||||
<h3><a name="send_req-5">send_req/5</a></h3> | |||||
<p><code>send_req(Url::string(), Headers::<a href="#type-headerList">headerList()</a>, Method::<a href="#type-method">method()</a>, Body::<a href="#type-body">body()</a>, Options::<a href="#type-optionList">optionList()</a>) -> <a href="#type-response">response()</a><ul><li><a name="type-optionList">optionList()</a> = [<a href="#type-option">option()</a>]</li><li><a name="type-option">option()</a> = {max_sessions, integer()} | {max_pipeline_size, integer()} | {trace, <a href="#type-boolean">boolean()</a>} | {is_ssl, <a href="#type-boolean">boolean()</a>} | {ssl_options, [SSLOpt]} | {pool_name, atom()} | {proxy_host, string()} | {proxy_port, integer()} | {proxy_user, string()} | {proxy_password, string()} | {use_absolute_uri, <a href="#type-boolean">boolean()</a>} | {basic_auth, {<a href="#type-username">username()</a>, <a href="#type-password">password()</a>}} | {cookie, string()} | {content_length, integer()} | {content_type, string()} | {save_response_to_file, <a href="#type-boolean">boolean()</a>} | {stream_to, <a href="#type-process">process()</a>} | {http_vsn, {MajorVsn, MinorVsn}}</li><li><a name="type-process">process()</a> = pid() | atom()</li><li><a name="type-username">username()</a> = string()</li><li><a name="type-password">password()</a> = string()</li><li>SSLOpt = term()</li></ul></code></p> | |||||
<p>Same as send_req/4. | |||||
For a description of SSL Options, look in the ssl manpage. If the | |||||
HTTP Version to use is not specified, the default is 1.1</p> | |||||
<h3><a name="send_req-6">send_req/6</a></h3> | |||||
<p><code>send_req(Url, Headers::<a href="#type-headerList">headerList()</a>, Method::<a href="#type-method">method()</a>, Body::<a href="#type-body">body()</a>, Options::<a href="#type-optionList">optionList()</a>, Timeout) -> <a href="#type-response">response()</a><ul><li>Timeout = integer() | infinity</li></ul></code></p> | |||||
<p>Same as send_req/5. | |||||
All timeout values are in milliseconds.</p> | |||||
<h3><a name="set_dest-3">set_dest/3</a></h3> | |||||
<p><code>set_dest(Host::string(), Port::integer(), Opts::<a href="#type-opt_list">opt_list()</a>) -> ok<ul><li><a name="type-opt_list">opt_list()</a> = [opt]</li><li><a name="type-opt">opt()</a> = {max_sessions, integer()} | {max_pipeline_size, integer()} | {trace, <a href="#type-boolean">boolean()</a>}</li></ul></code></p> | |||||
<p>Sets options for a destination. If the options have not been | |||||
set in the ibrowse.conf file, it can be set using this function | |||||
before sending the first request to the destination. If not, | |||||
defaults will be used. Entries in ibrowse.conf look like this. | |||||
<code><br> | |||||
{dest, Host, Port, MaxSess, MaxPipe, Options}.<br> | |||||
where <br> | |||||
Host = string(). "www.erlang.org" | "193.180.168.23"<br> | |||||
Port = integer()<br> | |||||
MaxSess = integer()<br> | |||||
MaxPipe = integer()<br> | |||||
Options = optionList() -- see options in send_req/5<br> | |||||
</code></p> | |||||
<h3><a name="shutting_down-0">shutting_down/0</a></h3> | |||||
<p><code>shutting_down() -> term()</code></p> | |||||
<p>Internal export. Called by a HTTP connection process to | |||||
indicate to ibrowse that it is shutting down and further requests | |||||
should not be sent it's way.</p> | |||||
<h3><a name="start-0">start/0</a></h3> | |||||
<p><code>start() -> term()</code></p> | |||||
<p> </p> | |||||
<h3><a name="start_link-0">start_link/0</a></h3> | |||||
<p><code>start_link() -> term()</code></p> | |||||
<p> </p> | |||||
<h3><a name="stop-0">stop/0</a></h3> | |||||
<p><code>stop() -> term()</code></p> | |||||
<p> </p> | |||||
<h3><a name="terminate-2">terminate/2</a></h3> | |||||
<p><code>terminate(Arg1, Arg2) -> term()</code></p> | |||||
<p> </p> | |||||
<h3><a name="trace_off-0">trace_off/0</a></h3> | |||||
<p><code>trace_off() -> term()</code></p> | |||||
<p>Turn tracing off for the ibrowse process</p> | |||||
<h3><a name="trace_off-2">trace_off/2</a></h3> | |||||
<p><code>trace_off(Host, Port) -> term()</code></p> | |||||
<p>Turn tracing OFF for all connections to the specified HTTP | |||||
server.</p> | |||||
<h3><a name="trace_on-0">trace_on/0</a></h3> | |||||
<p><code>trace_on() -> term()</code></p> | |||||
<p>Turn tracing on for the ibrowse process</p> | |||||
<h3><a name="trace_on-2">trace_on/2</a></h3> | |||||
<p><code>trace_on(Host, Port) -> term()<ul><li>Host = string()</li><li>Port = integer()</li></ul></code></p> | |||||
<p>Turn tracing on for all connections to the specified HTTP | |||||
server. Host is whatever is specified as the domain name in the URL</p></body> | |||||
</html> |
@ -0,0 +1,52 @@ | |||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> | |||||
<html> | |||||
<head> | |||||
<title>Module ibrowse_lib</title> | |||||
<link rel="stylesheet" type="text/css" href="stylesheet.css"> | |||||
</head> | |||||
<body bgcolor="white"> | |||||
<h1>Module ibrowse_lib</h1> | |||||
<ul><li> | |||||
<a href="#index">Function index</a></li><li> | |||||
<a href="#exported">Exported functions</a></li></ul> | |||||
<h2>Description</h2> | |||||
Module with a few useful functions | |||||
<h2><a name="index">Function Index</a></h2> | |||||
<table width="100%" border="1"><tr><th colspan="2" align="left">Exported Functions</th></tr> | |||||
<tr><td><a href="#decode_rfc822_date-1">decode_rfc822_date/1</a></td><td/></tr> | |||||
<tr><td><a href="#drv_ue-1">drv_ue/1</a></td><td/></tr> | |||||
<tr><td><a href="#drv_ue-2">drv_ue/2</a></td><td/></tr> | |||||
<tr><td><a href="#status_code-1">status_code/1</a></td><td>Given a status code, returns an atom describing the status code.</td></tr> | |||||
<tr><td><a href="#url_encode-1">url_encode/1</a></td><td>URL-encodes a string based on RFC 1738.</td></tr> | |||||
</table> | |||||
<h2><a name="exported">Exported Functions</a></h2> | |||||
<h3><a name="decode_rfc822_date-1">decode_rfc822_date/1</a></h3> | |||||
<p><code>decode_rfc822_date(Arg1) -> term()</code></p> | |||||
<p> </p> | |||||
<h3><a name="drv_ue-1">drv_ue/1</a></h3> | |||||
<p><code>drv_ue(Arg1) -> term()</code></p> | |||||
<p> </p> | |||||
<h3><a name="drv_ue-2">drv_ue/2</a></h3> | |||||
<p><code>drv_ue(Arg1, Arg2) -> term()</code></p> | |||||
<p> </p> | |||||
<h3><a name="status_code-1">status_code/1</a></h3> | |||||
<p><code>status_code(StatusCode) -> StatusDescription<ul><li>StatusCode = string() | integer()</li><li>StatusDescription = atom()</li></ul></code></p> | |||||
<p>Given a status code, returns an atom describing the status code.</p> | |||||
<h3><a name="url_encode-1">url_encode/1</a></h3> | |||||
<p><code>url_encode(Str) -> UrlEncodedStr<ul><li>Str = string()</li><li>UrlEncodedStr = string()</li></ul></code></p> | |||||
<p>URL-encodes a string based on RFC 1738. Returns a flat list.</p></body> | |||||
</html> |
@ -0,0 +1,17 @@ | |||||
%% Configuration file for specifying settings for HTTP servers which this | |||||
%% client will connect to. | |||||
%% The format of each entry is (one per line) | |||||
%% {dest, Hostname, Portnumber, MaxSessions, MaxPipelineSize, Options}. | |||||
%% | |||||
%% where Hostname = string() | |||||
%% Portnumber = integer() | |||||
%% MaxSessions = integer() | |||||
%% MaxPipelineSize = integer() | |||||
%% Options = [{Tag, Val} | ...] | |||||
%% Tag = term() | |||||
%% Value = term() | |||||
%% e.g. | |||||
%% {dest, "covig02", 8000, 10, 10, [{is_ssl, true}, {ssl_options, [option()]}]}. | |||||
%% If SSL is to be used, both the options, is_ssl and ssl_options MUST be specified | |||||
%% where option() is all options supported in the ssl module | |||||
@ -0,0 +1,6 @@ | |||||
'../src/ibrowse'. | |||||
'../src/ibrowse_http_client'. | |||||
'../src/ibrowse_app'. | |||||
'../src/ibrowse_sup'. | |||||
'../src/ibrowse_lib'. | |||||
'../src/ibrowse_test'. |
@ -0,0 +1,24 @@ | |||||
ERL_FILES = ibrowse.erl \ | |||||
ibrowse_http_client.erl \ | |||||
ibrowse_app.erl \ | |||||
ibrowse_sup.erl \ | |||||
ibrowse_lib.erl \ | |||||
ibrowse_test.erl | |||||
INCLUDE_DIRS = -I./ | |||||
ERLC ?= erlc | |||||
ERLC_EMULATOR ?= erl -boot start_clean | |||||
COMPILER_OPTIONS = -W +warn_unused_vars +nowarn_shadow_vars +warn_unused_import | |||||
.SUFFIXES: .erl .beam $(SUFFIXES) | |||||
EBIN = ../ebin | |||||
all: $(ERL_FILES:%.erl=$(EBIN)/%.beam) | |||||
$(EBIN)/%.beam: %.erl | |||||
${ERLC} $(COMPILER_OPTIONS) $(INCLUDE_DIRS) -o ../ebin $< | |||||
clean: | |||||
rm -f $(EBIN)/*.beam |
@ -0,0 +1,12 @@ | |||||
{application, ibrowse, | |||||
[{description, "HTTP client application"}, | |||||
{vsn, "%IBROWSE_VSN%"}, | |||||
{modules, [ ibrowse, | |||||
ibrowse_http_client, | |||||
ibrowse_app, | |||||
ibrowse_sup, | |||||
ibrowse_lib ]}, | |||||
{registered, []}, | |||||
{applications, [kernel,stdlib,sasl]}, | |||||
{env, []}, | |||||
{mod, {ibrowse_app, []}}]}. |
@ -0,0 +1,629 @@ | |||||
%%%------------------------------------------------------------------- | |||||
%%% File : ibrowse.erl | |||||
%%% Author : Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk> | |||||
%%% Description : Load balancer process for HTTP client connections. | |||||
%%% | |||||
%%% Created : 11 Oct 2003 by Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk> | |||||
%%%------------------------------------------------------------------- | |||||
%% @doc The ibrowse application implements an HTTP 1.1 client. This | |||||
%% module implements the API of the HTTP client. There is one named | |||||
%% process called 'ibrowse' which acts as a load balancer. There is | |||||
%% one process to handle one TCP connection to a webserver | |||||
%% (implemented in the module ibrowse_http_client). Multiple connections to a | |||||
%% webserver are setup based on the settings for each webserver. The | |||||
%% ibrowse process also determines which connection to pipeline a | |||||
%% certain request on. The functions to call are send_req/3, | |||||
%% send_req/4, send_req/5, send_req/6. | |||||
%% | |||||
%% <p>Here are a few sample invocations.</p> | |||||
%% | |||||
%% <code> | |||||
%% ibrowse:send_req("http://intranet/messenger/", [], get). | |||||
%% <br/><br/> | |||||
%% | |||||
%% ibrowse:send_req("http://www.google.com/", [], get, [], | |||||
%% [{proxy_user, "XXXXX"}, | |||||
%% {proxy_password, "XXXXX"}, | |||||
%% {proxy_host, "proxy"}, | |||||
%% {proxy_port, 8080}], 1000). | |||||
%% <br/><br/> | |||||
%% | |||||
%%ibrowse:send_req("http://www.erlang.org/download/otp_src_R10B-3.tar.gz", [], get, [], | |||||
%% [{proxy_user, "XXXXX"}, | |||||
%% {proxy_password, "XXXXX"}, | |||||
%% {proxy_host, "proxy"}, | |||||
%% {proxy_port, 8080}, | |||||
%% {save_response_to_file, true}], 1000). | |||||
%% <br/><br/> | |||||
%% | |||||
%% ibrowse:set_dest("www.hotmail.com", 80, [{max_sessions, 10}, | |||||
%% {max_pipeline_size, 1}]). | |||||
%% <br/><br/> | |||||
%% | |||||
%% ibrowse:send_req("http://www.erlang.org", [], head). | |||||
%% | |||||
%% <br/><br/> | |||||
%% ibrowse:send_req("http://www.sun.com", [], options). | |||||
%% | |||||
%% <br/><br/> | |||||
%% ibrowse:send_req("http://www.bbc.co.uk", [], trace). | |||||
%% | |||||
%% <br/><br/> | |||||
%% ibrowse:send_req("http://www.google.com", [], get, [], | |||||
%% [{stream_to, self()}]). | |||||
%% </code> | |||||
%% | |||||
%% <p>A driver exists which implements URL encoding in C, but the | |||||
%% speed achieved using only erlang has been good enough, so the | |||||
%% driver isn't actually used.</p> | |||||
-module(ibrowse). | |||||
-vsn('$Id: ibrowse.erl,v 1.1 2005/05/05 22:28:28 chandrusf Exp $ '). | |||||
-behaviour(gen_server). | |||||
%%-------------------------------------------------------------------- | |||||
%% Include files | |||||
%%-------------------------------------------------------------------- | |||||
%%-------------------------------------------------------------------- | |||||
%% External exports | |||||
-export([start_link/0, start/0, stop/0]). | |||||
%% gen_server callbacks | |||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, | |||||
terminate/2, code_change/3]). | |||||
%% API interface | |||||
-export([send_req/3, | |||||
send_req/4, | |||||
send_req/5, | |||||
send_req/6, | |||||
trace_on/0, | |||||
trace_off/0, | |||||
trace_on/2, | |||||
trace_off/2, | |||||
set_dest/3]). | |||||
%% Internal exports | |||||
-export([reply/2, | |||||
finished_async_request/0, | |||||
shutting_down/0]). | |||||
-ifdef(debug). | |||||
-compile(export_all). | |||||
-endif. | |||||
-import(ibrowse_http_client, [parse_url/1, | |||||
printable_date/0]). | |||||
-record(state, {dests=[], trace=false, port}). | |||||
-include("ibrowse.hrl"). | |||||
-define(DEF_MAX_SESSIONS,10). | |||||
-define(DEF_MAX_PIPELINE_SIZE,10). | |||||
%% key = {Host, Port} where Host is a string, or {Name, Host, Port} | |||||
%% where Name is an atom. | |||||
%% conns = queue() | |||||
-record(dest, {key, | |||||
conns=queue:new(), | |||||
num_sessions=0, | |||||
max_sessions=?DEF_MAX_SESSIONS, | |||||
max_pipeline_size=?DEF_MAX_PIPELINE_SIZE, | |||||
options=[], | |||||
trace=false}). | |||||
%%==================================================================== | |||||
%% External functions | |||||
%%==================================================================== | |||||
%%-------------------------------------------------------------------- | |||||
%% Function: start_link/0 | |||||
%% Description: Starts the server | |||||
%%-------------------------------------------------------------------- | |||||
start_link() -> | |||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). | |||||
start() -> | |||||
gen_server:start({local, ?MODULE}, ?MODULE, [], [{debug, []}]). | |||||
stop() -> | |||||
catch gen_server:call(ibrowse, stop). | |||||
%% @doc Sets options for a destination. If the options have not been | |||||
%% set in the ibrowse.conf file, it can be set using this function | |||||
%% before sending the first request to the destination. If not, | |||||
%% defaults will be used. Entries in ibrowse.conf look like this. | |||||
%% <code><br/> | |||||
%% {dest, Host, Port, MaxSess, MaxPipe, Options}.<br/> | |||||
%% where <br/> | |||||
%% Host = string(). "www.erlang.org" | "193.180.168.23"<br/> | |||||
%% Port = integer()<br/> | |||||
%% MaxSess = integer()<br/> | |||||
%% MaxPipe = integer()<br/> | |||||
%% Options = optionList() -- see options in send_req/5<br/> | |||||
%% </code> | |||||
%% @spec set_dest(Host::string(),Port::integer(),Opts::opt_list()) -> ok | |||||
%% opt_list() = [opt] | |||||
%% opt() = {max_sessions, integer()} | | |||||
%% {max_pipeline_size, integer()} | | |||||
%% {trace, boolean()} | |||||
set_dest(Host,Port,Opts) -> | |||||
gen_server:call(?MODULE,{set_dest,Host,Port,Opts}). | |||||
%% @doc This is the basic function to send a HTTP request. | |||||
%% The Status return value indicates the HTTP status code returned by the webserver | |||||
%% @spec send_req(Url::string(), Headers::headerList(), Method::method()) -> response() | |||||
%% headerList() = [{header(), value()}] | |||||
%% header() = atom() | string() | |||||
%% value() = term() | |||||
%% method() = get | post | head | options | put | delete | trace | |||||
%% Status = string() | |||||
%% ResponseHeaders = [respHeader()] | |||||
%% respHeader() = {headerName(), headerValue()} | |||||
%% headerName() = string() | |||||
%% headerValue() = string() | |||||
%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {error, Reason} | |||||
%% ResponseBody = string() | |||||
%% Reason = term() | |||||
send_req(Url, Headers, Method) -> | |||||
send_req(Url, Headers, Method, [], []). | |||||
%% @doc Same as send_req/3. | |||||
%% If a list is specified for the body it has to be a flat list. | |||||
%% @spec send_req(Url, Headers, Method::method(), Body::body()) -> response() | |||||
%% body() = [] | string() | binary() | |||||
send_req(Url, Headers, Method, Body) -> | |||||
send_req(Url, Headers, Method, Body, []). | |||||
%% @doc Same as send_req/4. | |||||
%% For a description of SSL Options, look in the ssl manpage. If the | |||||
%% HTTP Version to use is not specified, the default is 1.1 | |||||
%% @spec send_req(Url::string(), Headers::headerList(), Method::method(), Body::body(), Options::optionList()) -> response() | |||||
%% optionList() = [option()] | |||||
%% option() = {max_sessions, integer()} | | |||||
%% {max_pipeline_size, integer()} | | |||||
%% {trace, boolean()} | | |||||
%% {is_ssl, boolean()} | | |||||
%% {ssl_options, [SSLOpt]} | | |||||
%% {pool_name, atom()} | | |||||
%% {proxy_host, string()} | | |||||
%% {proxy_port, integer()} | | |||||
%% {proxy_user, string()} | | |||||
%% {proxy_password, string()} | | |||||
%% {use_absolute_uri, boolean()} | | |||||
%% {basic_auth, {username(), password()}} | | |||||
%% {cookie, string()} | | |||||
%% {content_length, integer()} | | |||||
%% {content_type, string()} | | |||||
%% {save_response_to_file, boolean()} | | |||||
%% {stream_to, process()} | | |||||
%% {http_vsn, {MajorVsn, MinorVsn}} | |||||
%% process() = pid() | atom() | |||||
%% username() = string() | |||||
%% password() = string() | |||||
%% SSLOpt = term() | |||||
send_req(Url, Headers, Method, Body, Options) -> | |||||
send_req(Url, Headers, Method, Body, Options, 30000). | |||||
%% @doc Same as send_req/5. | |||||
%% All timeout values are in milliseconds. | |||||
%% @spec send_req(Url, Headers::headerList(), Method::method(), Body::body(), Options::optionList(), Timeout) -> response() | |||||
%% Timeout = integer() | infinity | |||||
send_req(Url, Headers, Method, Body, Options, Timeout) -> | |||||
Timeout_1 = case Timeout of | |||||
infinity -> | |||||
infinity; | |||||
_ -> | |||||
Timeout + 1000 | |||||
end, | |||||
case catch gen_server:call(ibrowse, | |||||
{send_req, [Url, Headers, Method, | |||||
Body, Options, Timeout]}, | |||||
Timeout_1) of | |||||
{'EXIT', {timeout, _}} -> | |||||
{error, genserver_timedout}; | |||||
Res -> | |||||
Res | |||||
end. | |||||
%% @doc Turn tracing on for the ibrowse process | |||||
trace_on() -> | |||||
ibrowse ! {trace, true}. | |||||
%% @doc Turn tracing off for the ibrowse process | |||||
trace_off() -> | |||||
ibrowse ! {trace, false}. | |||||
%% @doc Turn tracing on for all connections to the specified HTTP | |||||
%% server. Host is whatever is specified as the domain name in the URL | |||||
%% @spec trace_on(Host, Port) -> term() | |||||
%% Host = string() | |||||
%% Port = integer() | |||||
trace_on(Host, Port) -> | |||||
ibrowse ! {trace, true, Host, Port}. | |||||
%% @doc Turn tracing OFF for all connections to the specified HTTP | |||||
%% server. | |||||
%% @spec trace_off(Host, Port) -> term() | |||||
trace_off(Host, Port) -> | |||||
ibrowse ! {trace, false, Host, Port}. | |||||
%% @doc Internal export. Called by a HTTP connection process to | |||||
%% indicate to the load balancing process (ibrowse) that a synchronous | |||||
%% request has finished processing. | |||||
reply(OrigCaller, Reply) -> | |||||
gen_server:call(ibrowse, {reply, OrigCaller, Reply, self()}). | |||||
%% @doc Internal export. Called by a HTTP connection process to | |||||
%% indicate to the load balancing process (ibrowse) that an | |||||
%% asynchronous request has finished processing. | |||||
finished_async_request() -> | |||||
gen_server:call(ibrowse, {finished_async_request, self()}). | |||||
%% @doc Internal export. Called by a HTTP connection process to | |||||
%% indicate to ibrowse that it is shutting down and further requests | |||||
%% should not be sent it's way. | |||||
shutting_down() -> | |||||
gen_server:call(ibrowse, {shutting_down, self()}). | |||||
%%==================================================================== | |||||
%% Server functions | |||||
%%==================================================================== | |||||
%%-------------------------------------------------------------------- | |||||
%% Function: init/1 | |||||
%% Description: Initiates the server | |||||
%% Returns: {ok, State} | | |||||
%% {ok, State, Timeout} | | |||||
%% ignore | | |||||
%% {stop, Reason} | |||||
%%-------------------------------------------------------------------- | |||||
init(_) -> | |||||
process_flag(trap_exit, true), | |||||
State = #state{}, | |||||
put(my_trace_flag, State#state.trace), | |||||
case code:priv_dir(ibrowse) of | |||||
{error, _} -> | |||||
{ok, #state{}}; | |||||
PrivDir -> | |||||
Filename = filename:join(PrivDir, "ibrowse.conf"), | |||||
case file:consult(Filename) of | |||||
{ok, Terms} -> | |||||
Fun = fun({dest, Host, Port, MaxSess, MaxPipe, Options}, Acc) | |||||
when list(Host), integer(Port), | |||||
integer(MaxSess), MaxSess > 0, | |||||
integer(MaxPipe), MaxPipe > 0, list(Options) -> | |||||
Key = maybe_named_key(Host, Port, Options), | |||||
NewDest = #dest{key=Key, | |||||
options=Options, | |||||
max_sessions=MaxSess, | |||||
max_pipeline_size=MaxPipe}, | |||||
[NewDest | Acc]; | |||||
(_, Acc) -> | |||||
Acc | |||||
end, | |||||
{ok, #state{dests=lists:foldl(Fun, [], Terms)}}; | |||||
_Else -> | |||||
{ok, #state{}} | |||||
end | |||||
end. | |||||
%%-------------------------------------------------------------------- | |||||
%% Function: handle_call/3 | |||||
%% Description: Handling call messages | |||||
%% Returns: {reply, Reply, State} | | |||||
%% {reply, Reply, State, Timeout} | | |||||
%% {noreply, State} | | |||||
%% {noreply, State, Timeout} | | |||||
%% {stop, Reason, Reply, State} | (terminate/2 is called) | |||||
%% {stop, Reason, State} (terminate/2 is called) | |||||
%%-------------------------------------------------------------------- | |||||
handle_call({send_req, _}=Req, From, State) -> | |||||
State_1 = handle_send_req(Req, From, State), | |||||
{noreply, State_1}; | |||||
handle_call({reply, OrigCaller, Reply, HttpClientPid}, From, State) -> | |||||
gen_server:reply(From, ok), | |||||
gen_server:reply(OrigCaller, Reply), | |||||
Key = {HttpClientPid, pending_reqs}, | |||||
case get(Key) of | |||||
NumPend when integer(NumPend) -> | |||||
put(Key, NumPend - 1); | |||||
_ -> | |||||
ok | |||||
end, | |||||
{noreply, State}; | |||||
handle_call({finished_async_request, HttpClientPid}, From, State) -> | |||||
gen_server:reply(From, ok), | |||||
Key = {HttpClientPid, pending_reqs}, | |||||
case get(Key) of | |||||
NumPend when integer(NumPend) -> | |||||
put(Key, NumPend - 1); | |||||
_ -> | |||||
ok | |||||
end, | |||||
{noreply, State}; | |||||
handle_call({shutting_down, Pid}, _From, State) -> | |||||
State_1 = handle_conn_closing(Pid, State), | |||||
{reply, ok, State_1}; | |||||
handle_call({set_dest,Host,Port,Opts}, _From, State) -> | |||||
State2 = set_destI(State,Host,Port,Opts), | |||||
{reply, ok, State2}; | |||||
handle_call(stop, _From, State) -> | |||||
{stop, shutting_down, ok, State}; | |||||
handle_call(_Request, _From, State) -> | |||||
Reply = ok, | |||||
{reply, Reply, State}. | |||||
%%-------------------------------------------------------------------- | |||||
%% Function: handle_cast/2 | |||||
%% Description: Handling cast messages | |||||
%% Returns: {noreply, State} | | |||||
%% {noreply, State, Timeout} | | |||||
%% {stop, Reason, State} (terminate/2 is called) | |||||
%%-------------------------------------------------------------------- | |||||
handle_cast(_Msg, State) -> | |||||
{noreply, State}. | |||||
%%-------------------------------------------------------------------- | |||||
%% Function: handle_info/2 | |||||
%% Description: Handling all non call/cast messages | |||||
%% Returns: {noreply, State} | | |||||
%% {noreply, State, Timeout} | | |||||
%% {stop, Reason, State} (terminate/2 is called) | |||||
%%-------------------------------------------------------------------- | |||||
%% A bit of a bodge here...ideally, would be good to store connection state | |||||
%% in the queue itself against each Pid. | |||||
handle_info({done_req, Pid}, State) -> | |||||
Key = {Pid, pending_reqs}, | |||||
case get(Key) of | |||||
NumPend when integer(NumPend) -> | |||||
put(Key, NumPend - 1); | |||||
_ -> | |||||
ok | |||||
end, | |||||
do_trace("~p has finished a request~n", [Pid]), | |||||
{noreply, State}; | |||||
handle_info({'EXIT', _, normal}, State) -> | |||||
{noreply, State}; | |||||
handle_info({'EXIT', Pid, _Reason}, State) -> | |||||
%% TODO: We have to reply to all the pending requests | |||||
State_1 = handle_conn_closing(Pid, State), | |||||
do_trace("~p has exited~n", [Pid]), | |||||
{noreply, State_1}; | |||||
handle_info({shutting_down, Pid}, State) -> | |||||
State_1 = handle_conn_closing(Pid, State), | |||||
{noreply, State_1}; | |||||
handle_info({conn_closing, Pid, OriReq, From}, State) -> | |||||
State_1 = handle_conn_closing(Pid, State), | |||||
State_2 = handle_send_req(OriReq, From, State_1), | |||||
{noreply, State_2}; | |||||
handle_info({trace, Bool}, State) -> | |||||
put(my_trace_flag, Bool), | |||||
{noreply, State#state{trace=Bool}}; | |||||
handle_info({trace, Bool, Host, Port}, #state{dests=Dests}=State) -> | |||||
case lists:keysearch({Host, Port}, #dest.key, Dests) of | |||||
{value, Dest} -> | |||||
lists:foreach(fun(ConnPid) -> | |||||
ConnPid ! {trace, Bool} | |||||
end, queue:to_list(Dest#dest.conns)), | |||||
{noreply, State#state{dests=lists:keyreplace({Host,Port}, #dest.key, Dests, Dest#dest{trace=Bool})}}; | |||||
false -> | |||||
do_trace("Not found any state information for specified Host, Port.~n", []), | |||||
{noreply, State} | |||||
end; | |||||
handle_info(_Info, State) -> | |||||
{noreply, State}. | |||||
%%-------------------------------------------------------------------- | |||||
%% Function: terminate/2 | |||||
%% Description: Shutdown the server | |||||
%% Returns: any (ignored by gen_server) | |||||
%%-------------------------------------------------------------------- | |||||
terminate(_Reason, _State) -> | |||||
ok. | |||||
%%-------------------------------------------------------------------- | |||||
%% Func: code_change/3 | |||||
%% Purpose: Convert process state when code is changed | |||||
%% Returns: {ok, NewState} | |||||
%%-------------------------------------------------------------------- | |||||
code_change(_OldVsn, State, _Extra) -> | |||||
{ok, State}. | |||||
%%-------------------------------------------------------------------- | |||||
%%% Internal functions | |||||
%%-------------------------------------------------------------------- | |||||
handle_send_req({send_req, [Url, _Headers, _Method, _Body, Options, _Timeout]}=Req, | |||||
From, State) -> | |||||
case get_host_port(Url, Options) of | |||||
{Host, Port, _RelPath} -> | |||||
Key = maybe_named_key(Host, Port, Options), | |||||
case lists:keysearch(Key, #dest.key, State#state.dests) of | |||||
false -> | |||||
{ok, Pid} = spawn_new_connection(Key, false, Options), | |||||
Pid ! {Req, From}, | |||||
Q = queue:new(), | |||||
Q_1 = queue:in(Pid, Q), | |||||
NewDest = #dest{key=Key,conns=Q_1,num_sessions=1}, %% MISSING is_ssl | |||||
State#state{dests=[NewDest|State#state.dests]}; | |||||
{value, #dest{conns=Conns, | |||||
num_sessions=NumS, | |||||
max_pipeline_size=MaxPSz, | |||||
max_sessions=MaxS}=Dest} -> | |||||
case get_free_worker(Conns, NumS, MaxS, MaxPSz) of | |||||
spawn_new_connection -> | |||||
do_trace("Spawning new connection~n", []), | |||||
{ok, Pid} = spawn_new_connection(Key, Dest#dest.trace, Dest#dest.options), | |||||
Pid ! {Req, From}, | |||||
Q_1 = queue:in(Pid, Conns), | |||||
Dest_1 = Dest#dest{conns=Q_1, num_sessions=NumS+1}, | |||||
State#state{dests=lists:keyreplace(Key, #dest.key, State#state.dests, Dest_1)}; | |||||
not_found -> | |||||
do_trace("State -> ~p~nPDict -> ~p~n", [State, get()]), | |||||
gen_server:reply(From, {error, retry_later}), | |||||
State; | |||||
{ok, Pid, _, ConnPids} -> | |||||
do_trace("Reusing existing pid: ~p~n", [Pid]), | |||||
Pid_key = {Pid, pending_reqs}, | |||||
put(Pid_key, get(Pid_key) + 1), | |||||
Pid ! {Req, From}, | |||||
State#state{dests=lists:keyreplace(Key, #dest.key, State#state.dests,Dest#dest{conns=ConnPids})} | |||||
end | |||||
end; | |||||
invalid_uri -> | |||||
gen_server:reply(From, {error, invalid_uri}), | |||||
State | |||||
end. | |||||
get_host_port(Url, Options) -> | |||||
case get_value(proxy_host, Options, false) of | |||||
false -> | |||||
case parse_url(Url) of | |||||
#url{host=H, port=P, path=Path} -> | |||||
{H, P, Path}; | |||||
_Err -> | |||||
invalid_uri | |||||
end; | |||||
PxyHost -> | |||||
PxyPort = get_value(proxy_port, Options, 80), | |||||
{PxyHost, PxyPort, Url} | |||||
end. | |||||
handle_conn_closing(Pid, #state{dests=Dests}=State) -> | |||||
erase({Pid, pending_reqs}), | |||||
HostKey = get({Pid, hostport}), | |||||
erase({Pid, hostport}), | |||||
do_trace("~p is shutting down~n", [Pid]), | |||||
case lists:keysearch(HostKey, #dest.key, Dests) of | |||||
{value, #dest{num_sessions=Num, conns=Q}=Dest} -> | |||||
State#state{dests=lists:keyreplace(HostKey, #dest.key, Dests, | |||||
Dest#dest{conns=del_from_q(Q, Num, Pid), num_sessions=Num-1})}; | |||||
false -> | |||||
State | |||||
end. | |||||
%% Replaces destination information if found, otherwise appends it. | |||||
%% Copies over Connection Queue and Number of sessions. | |||||
set_destI(State,Host,Port,Opts) -> | |||||
#state{dests=DestList} = State, | |||||
Key = maybe_named_key(Host, Port, Opts), | |||||
NewDests = case lists:keysearch(Key, #dest.key, DestList) of | |||||
false -> | |||||
Dest = insert_opts(Opts,#dest{key=Key}), | |||||
[Dest | DestList]; | |||||
{value, OldDest} -> | |||||
OldDest_1 = insert_opts(Opts, OldDest), | |||||
[OldDest_1 | (DestList -- [OldDest])] | |||||
end, | |||||
State#state{dests=NewDests}. | |||||
insert_opts(Opts, Dest) -> | |||||
insert_opts_1(Opts, Dest#dest{options=Opts}). | |||||
insert_opts_1([],Dest) -> Dest; | |||||
insert_opts_1([{max_sessions,Msess}|T],Dest) -> | |||||
insert_opts_1(T,Dest#dest{max_sessions=Msess}); | |||||
insert_opts_1([{max_pipeline_size,Mpls}|T],Dest) -> | |||||
insert_opts_1(T,Dest#dest{max_pipeline_size=Mpls}); | |||||
insert_opts_1([{trace,Bool}|T],Dest) when Bool==true; Bool==false -> | |||||
insert_opts_1(T,Dest#dest{trace=Bool}); | |||||
insert_opts_1([_|T],Dest) -> %% ignores other | |||||
insert_opts_1(T,Dest). | |||||
% Picks out the worker with the minimum pipeline size | |||||
% If a worker is found with a non-zero pipeline size, but the number of sessins | |||||
% is less than the max allowed sessions, a new connection is spawned. | |||||
get_free_worker(Q, NumSessions, MaxSessions, MaxPSz) -> | |||||
case get_free_worker_1(Q, NumSessions, MaxPSz, {undefined, undefined}) of | |||||
not_found when NumSessions < MaxSessions -> | |||||
spawn_new_connection; | |||||
not_found -> | |||||
not_found; | |||||
{ok, Pid, PSz, _Q1} when NumSessions < MaxSessions, PSz > 0 -> | |||||
do_trace("Found Pid -> ~p. PSz -> ~p~n", [Pid, PSz]), | |||||
spawn_new_connection; | |||||
Ret -> | |||||
do_trace("get_free_worker: Ret -> ~p~n", [Ret]), | |||||
Ret | |||||
end. | |||||
get_free_worker_1(_, 0, _, {undefined, undefined}) -> | |||||
not_found; | |||||
get_free_worker_1({{value, WorkerPid}, Q}, 0, _, {MinPSzPid, PSz}) -> | |||||
{ok, MinPSzPid, PSz, queue:in(WorkerPid, Q)}; | |||||
get_free_worker_1({{value, Pid}, Q1}, NumSessions, MaxPSz, {_MinPSzPid, MinPSz}=V) -> | |||||
do_trace("Pid -> ~p. MaxPSz -> ~p MinPSz -> ~p~n", [Pid, MaxPSz, MinPSz]), | |||||
case get({Pid, pending_reqs}) of | |||||
NumP when NumP < MaxPSz, NumP < MinPSz -> | |||||
get_free_worker_1(queue:out(queue:in(Pid, Q1)), NumSessions-1, MaxPSz, {Pid, NumP}); | |||||
_ -> | |||||
get_free_worker_1(queue:out(queue:in(Pid, Q1)), NumSessions-1, MaxPSz, V) | |||||
end; | |||||
get_free_worker_1({empty, _Q}, _, _, _) -> | |||||
do_trace("Queue empty -> not_found~n", []), | |||||
not_found; | |||||
get_free_worker_1(Q, NumSessions, MaxPSz, MinPSz) -> | |||||
get_free_worker_1(queue:out(Q), NumSessions, MaxPSz, MinPSz). | |||||
spawn_new_connection({_Pool_name, Host, Port}, Trace, Options) -> | |||||
spawn_new_connection({Host, Port}, Trace, Options); | |||||
spawn_new_connection({Host, Port}, Trace, Options) -> | |||||
{ok, Pid} = ibrowse_http_client:start_link([Host, Port, Trace, Options]), | |||||
Key = maybe_named_key(Host, Port, Options), | |||||
put({Pid, pending_reqs}, 1), | |||||
put({Pid, hostport}, Key), | |||||
{ok, Pid}. | |||||
del_from_q({empty, Q}, _, _) -> | |||||
Q; | |||||
del_from_q({{value, V}, Q}, 0, _Elem) -> | |||||
queue:in(V, Q); | |||||
del_from_q({{value, Elem}, Q1}, QSize, Elem) -> | |||||
del_from_q(queue:out(Q1), QSize-1, Elem); | |||||
del_from_q({{value, V}, Q}, QSize, Elem) -> | |||||
del_from_q(queue:out(queue:in(V, Q)), QSize-1, Elem); | |||||
del_from_q(Q, QSize, Elem) -> | |||||
del_from_q(queue:out(Q), QSize, Elem). | |||||
maybe_named_key(Host, Port, Opts) -> | |||||
case lists:keysearch(name, 1, Opts) of | |||||
{value, {name, Pool_name}} when is_atom(Pool_name) -> | |||||
{Pool_name, Host, Port}; | |||||
_ -> | |||||
{Host, Port} | |||||
end. | |||||
% get_value(Tag, TVL) -> | |||||
% {value, {_, V}} = lists:keysearch(Tag,1,TVL), | |||||
% V. | |||||
get_value(Tag, TVL, DefVal) -> | |||||
case lists:keysearch(Tag, 1, TVL) of | |||||
{value, {_, V}} -> | |||||
V; | |||||
false -> | |||||
DefVal | |||||
end. | |||||
do_trace(Fmt, Args) -> | |||||
do_trace(get(my_trace_flag), Fmt, Args). | |||||
% do_trace(true, Fmt, Args) -> | |||||
% io:format("~s -- IBROWSE - "++Fmt, [printable_date() | Args]); | |||||
do_trace(true, Fmt, Args) -> | |||||
io:format("~s -- IBROWSE - "++Fmt, [printable_date() | Args]); | |||||
do_trace(_, _, _) -> ok. |
@ -0,0 +1,6 @@ | |||||
-ifndef(IBROWSE_HRL). | |||||
-define(IBROWSE_HRL, "ibrowse.hrl"). | |||||
-record(url, {abspath, host, port, username, password, path, protocol}). | |||||
-endif. |
@ -0,0 +1,64 @@ | |||||
%%%------------------------------------------------------------------- | |||||
%%% File : ibrowse_app.erl | |||||
%%% Author : Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk> | |||||
%%% Description : | |||||
%%% | |||||
%%% Created : 15 Oct 2003 by Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk> | |||||
%%%------------------------------------------------------------------- | |||||
-module(ibrowse_app). | |||||
-vsn('$Id: ibrowse_app.erl,v 1.1 2005/05/05 22:28:28 chandrusf Exp $ '). | |||||
-behaviour(application). | |||||
%%-------------------------------------------------------------------- | |||||
%% Include files | |||||
%%-------------------------------------------------------------------- | |||||
%%-------------------------------------------------------------------- | |||||
%% External exports | |||||
%%-------------------------------------------------------------------- | |||||
-export([ | |||||
start/2, | |||||
stop/1 | |||||
]). | |||||
%%-------------------------------------------------------------------- | |||||
%% Internal exports | |||||
%%-------------------------------------------------------------------- | |||||
-export([ | |||||
]). | |||||
%%-------------------------------------------------------------------- | |||||
%% Macros | |||||
%%-------------------------------------------------------------------- | |||||
%%-------------------------------------------------------------------- | |||||
%% Records | |||||
%%-------------------------------------------------------------------- | |||||
%%==================================================================== | |||||
%% External functions | |||||
%%==================================================================== | |||||
%%-------------------------------------------------------------------- | |||||
%% Func: start/2 | |||||
%% Returns: {ok, Pid} | | |||||
%% {ok, Pid, State} | | |||||
%% {error, Reason} | |||||
%%-------------------------------------------------------------------- | |||||
start(_Type, _StartArgs) -> | |||||
case ibrowse_sup:start_link() of | |||||
{ok, Pid} -> | |||||
{ok, Pid}; | |||||
Error -> | |||||
Error | |||||
end. | |||||
%%-------------------------------------------------------------------- | |||||
%% Func: stop/1 | |||||
%% Returns: any | |||||
%%-------------------------------------------------------------------- | |||||
stop(_State) -> | |||||
ok. | |||||
%%==================================================================== | |||||
%% Internal functions | |||||
%%==================================================================== |
@ -0,0 +1,141 @@ | |||||
%%% File : ibrowse_lib.erl | |||||
%%% Author : Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk> | |||||
%%% Description : | |||||
%%% Created : 27 Feb 2004 by Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk> | |||||
%% @doc Module with a few useful functions | |||||
-module(ibrowse_lib). | |||||
-vsn('$Id: ibrowse_lib.erl,v 1.1 2005/05/05 22:28:28 chandrusf Exp $ '). | |||||
-author('chandru'). | |||||
-ifdef(debug). | |||||
-compile(export_all). | |||||
-endif. | |||||
-export([url_encode/1, | |||||
decode_rfc822_date/1, | |||||
status_code/1, | |||||
drv_ue/1, | |||||
drv_ue/2]). | |||||
drv_ue(Str) -> | |||||
[{port, Port}| _] = ets:lookup(ibrowse_table, port), | |||||
drv_ue(Str, Port). | |||||
drv_ue(Str, Port) -> | |||||
case erlang:port_control(Port, 1, Str) of | |||||
[] -> | |||||
Str; | |||||
Res -> | |||||
Res | |||||
end. | |||||
%% @doc URL-encodes a string based on RFC 1738. Returns a flat list. | |||||
%% @spec url_encode(Str) -> UrlEncodedStr | |||||
%% Str = string() | |||||
%% UrlEncodedStr = string() | |||||
url_encode(Str) when list(Str) -> | |||||
url_encode_char(lists:reverse(Str), []). | |||||
url_encode_char([X | T], Acc) when X >= $a, X =< $z -> | |||||
url_encode_char(T, [X | Acc]); | |||||
url_encode_char([X | T], Acc) when X >= $A, X =< $Z -> | |||||
url_encode_char(T, [X | Acc]); | |||||
url_encode_char([X | T], Acc) when X == $-; X == $_; X == $. -> | |||||
url_encode_char(T, [X | Acc]); | |||||
url_encode_char([32 | T], Acc) -> | |||||
url_encode_char(T, [$+ | Acc]); | |||||
url_encode_char([X | T], Acc) -> | |||||
url_encode_char(T, [$%, d2h(X bsr 4), d2h(X band 16#0f) | Acc]); | |||||
url_encode_char([], Acc) -> | |||||
Acc. | |||||
d2h(N) when N<10 -> N+$0; | |||||
d2h(N) -> N+$a-10. | |||||
decode_rfc822_date(String) when list(String) -> | |||||
case catch decode_rfc822_date_1(string:tokens(String, ", \t\r\n")) of | |||||
{'EXIT', _} -> | |||||
{error, invalid_date}; | |||||
Res -> | |||||
Res | |||||
end. | |||||
% TODO: Have to handle the Zone | |||||
decode_rfc822_date_1([_,DayInt,Month,Year, Time,Zone]) -> | |||||
decode_rfc822_date_1([DayInt,Month,Year, Time,Zone]); | |||||
decode_rfc822_date_1([Day,Month,Year, Time,_Zone]) -> | |||||
DayI = list_to_integer(Day), | |||||
MonthI = month_int(Month), | |||||
YearI = list_to_integer(Year), | |||||
TimeTup = case string:tokens(Time, ":") of | |||||
[H,M] -> | |||||
{list_to_integer(H), | |||||
list_to_integer(M), | |||||
0}; | |||||
[H,M,S] -> | |||||
{list_to_integer(H), | |||||
list_to_integer(M), | |||||
list_to_integer(S)} | |||||
end, | |||||
{{YearI,MonthI,DayI}, TimeTup}. | |||||
month_int("Jan") -> 1; | |||||
month_int("Feb") -> 2; | |||||
month_int("Mar") -> 3; | |||||
month_int("Apr") -> 4; | |||||
month_int("May") -> 5; | |||||
month_int("Jun") -> 6; | |||||
month_int("Jul") -> 7; | |||||
month_int("Aug") -> 8; | |||||
month_int("Sep") -> 9; | |||||
month_int("Oct") -> 10; | |||||
month_int("Nov") -> 11; | |||||
month_int("Dec") -> 12. | |||||
%% @doc Given a status code, returns an atom describing the status code. | |||||
%% @spec status_code(StatusCode) -> StatusDescription | |||||
%% StatusCode = string() | integer() | |||||
%% StatusDescription = atom() | |||||
status_code(100) -> continue; | |||||
status_code(101) -> switching_protocols; | |||||
status_code(200) -> ok; | |||||
status_code(201) -> created; | |||||
status_code(202) -> accepted; | |||||
status_code(203) -> non_authoritative_information; | |||||
status_code(204) -> no_content; | |||||
status_code(205) -> reset_content; | |||||
status_code(206) -> partial_content; | |||||
status_code(300) -> multiple_choices; | |||||
status_code(301) -> moved_permanently; | |||||
status_code(302) -> found; | |||||
status_code(303) -> see_other; | |||||
status_code(304) -> not_modified; | |||||
status_code(305) -> use_proxy; | |||||
status_code(306) -> unused; | |||||
status_code(307) -> temporary_redirect; | |||||
status_code(400) -> bad_request; | |||||
status_code(401) -> unauthorized; | |||||
status_code(402) -> payment_required; | |||||
status_code(403) -> forbidden; | |||||
status_code(404) -> not_found; | |||||
status_code(405) -> method_not_allowed; | |||||
status_code(406) -> not_acceptable; | |||||
status_code(407) -> proxy_authentication_required; | |||||
status_code(408) -> request_tunnel; | |||||
status_code(408) -> request_timeout; | |||||
status_code(409) -> conflict; | |||||
status_code(410) -> gone; | |||||
status_code(411) -> length_required; | |||||
status_code(412) -> precondition_failed; | |||||
status_code(413) -> request_entity_too_large; | |||||
status_code(414) -> request_uri_too_long; | |||||
status_code(415) -> unsupported_media_type; | |||||
status_code(416) -> requested_range_not_satisfiable; | |||||
status_code(417) -> expectation_failed; | |||||
status_code(500) -> internal_server_error; | |||||
status_code(501) -> not_implemented; | |||||
status_code(502) -> bad_gateway; | |||||
status_code(503) -> service_unavailable; | |||||
status_code(504) -> gateway_timeout; | |||||
status_code(505) -> http_version_not_supported; | |||||
status_code(X) when is_list(X) -> status_code(list_to_integer(X)); | |||||
status_code(_) -> unknown_status_code. |
@ -0,0 +1,65 @@ | |||||
%%%------------------------------------------------------------------- | |||||
%%% File : ibrowse_sup.erl | |||||
%%% Author : Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk> | |||||
%%% Description : | |||||
%%% | |||||
%%% Created : 15 Oct 2003 by Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk> | |||||
%%%------------------------------------------------------------------- | |||||
-module(ibrowse_sup). | |||||
-vsn('$Id: ibrowse_sup.erl,v 1.1 2005/05/05 22:28:28 chandrusf Exp $ '). | |||||
-behaviour(supervisor). | |||||
%%-------------------------------------------------------------------- | |||||
%% Include files | |||||
%%-------------------------------------------------------------------- | |||||
%%-------------------------------------------------------------------- | |||||
%% External exports | |||||
%%-------------------------------------------------------------------- | |||||
-export([ | |||||
start_link/0 | |||||
]). | |||||
%%-------------------------------------------------------------------- | |||||
%% Internal exports | |||||
%%-------------------------------------------------------------------- | |||||
-export([ | |||||
init/1 | |||||
]). | |||||
%%-------------------------------------------------------------------- | |||||
%% Macros | |||||
%%-------------------------------------------------------------------- | |||||
-define(SERVER, ?MODULE). | |||||
%%-------------------------------------------------------------------- | |||||
%% Records | |||||
%%-------------------------------------------------------------------- | |||||
%%==================================================================== | |||||
%% External functions | |||||
%%==================================================================== | |||||
%%-------------------------------------------------------------------- | |||||
%% Function: start_link/0 | |||||
%% Description: Starts the supervisor | |||||
%%-------------------------------------------------------------------- | |||||
start_link() -> | |||||
supervisor:start_link({local, ?SERVER}, ?MODULE, []). | |||||
%%==================================================================== | |||||
%% Server functions | |||||
%%==================================================================== | |||||
%%-------------------------------------------------------------------- | |||||
%% Func: init/1 | |||||
%% Returns: {ok, {SupFlags, [ChildSpec]}} | | |||||
%% ignore | | |||||
%% {error, Reason} | |||||
%%-------------------------------------------------------------------- | |||||
init([]) -> | |||||
AChild = {ibrowse,{ibrowse,start_link,[]}, | |||||
permanent,2000,worker,[ibrowse, ibrowse_http_client]}, | |||||
{ok,{{one_for_all,10,1}, [AChild]}}. | |||||
%%==================================================================== | |||||
%% Internal functions | |||||
%%==================================================================== |
@ -0,0 +1,74 @@ | |||||
%%% File : ibrowse_test.erl | |||||
%%% Author : Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk> | |||||
%%% Description : Test ibrowse | |||||
%%% Created : 14 Oct 2003 by Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk> | |||||
-module(ibrowse_test). | |||||
-vsn('$Id: ibrowse_test.erl,v 1.1 2005/05/05 22:28:28 chandrusf Exp $ '). | |||||
-compile(export_all). | |||||
-import(ibrowse_http_client, [printable_date/0]). | |||||
send_reqs(Url, NumWorkers, NumReqsPerWorker) -> | |||||
proc_lib:spawn(?MODULE, send_reqs_1, [Url, NumWorkers, NumReqsPerWorker]). | |||||
send_reqs_1(Url, NumWorkers, NumReqsPerWorker) -> | |||||
process_flag(trap_exit, true), | |||||
Pids = lists:map(fun(_X) -> | |||||
proc_lib:spawn_link(?MODULE, do_send_req, [Url, NumReqsPerWorker, self()]) | |||||
end, lists:seq(1,NumWorkers)), | |||||
put(num_reqs_per_worker, NumReqsPerWorker), | |||||
do_wait(Pids, now(), printable_date(), 0, 0). | |||||
do_wait([], _StartNow, StartTime, NumSucc, NumErrs) -> | |||||
io:format("~n~nDone...~nStartTime -> ~s~n", [StartTime]), | |||||
io:format("EndTime -> ~s~n", [printable_date()]), | |||||
io:format("NumSucc -> ~p~n", [NumSucc]), | |||||
io:format("NumErrs -> ~p~n", [NumErrs]); | |||||
do_wait(Pids, StartNow, StartTime, NumSucc, NumErrs) -> | |||||
receive | |||||
{done, From, _Time, {ChildNumSucc, ChildNumFail}} -> | |||||
do_wait(Pids--[From], StartNow, StartTime, NumSucc+ChildNumSucc, NumErrs+ChildNumFail); | |||||
{'EXIT',_, normal} -> | |||||
do_wait(Pids, StartNow, StartTime, NumSucc, NumErrs); | |||||
{'EXIT', From, _Reason} -> | |||||
do_wait(Pids--[From], StartNow, StartTime, NumSucc, NumErrs + get(num_reqs_per_worker)) | |||||
end. | |||||
do_send_req(Url, NumReqs, Parent) -> | |||||
StartTime = now(), | |||||
Res = do_send_req_1(Url, NumReqs, {0, 0}), | |||||
Parent ! {done, self(), StartTime, Res}. | |||||
do_send_req_1(_Url, 0, {NumSucc, NumFail}) -> | |||||
{NumSucc, NumFail}; | |||||
do_send_req_1(Url, NumReqs, {NumSucc, NumFail}) -> | |||||
case ibrowse:send_req(Url, [], get, [], [], 10000) of | |||||
{ok, _Status, _Headers, _Body} -> | |||||
do_send_req_1(Url, NumReqs-1, {NumSucc+1, NumFail}); | |||||
_Err -> | |||||
do_send_req_1(Url, NumReqs-1, {NumSucc, NumFail+1}) | |||||
end. | |||||
drv_ue_test() -> | |||||
drv_ue_test(lists:duplicate(1024, 127)). | |||||
drv_ue_test(Data) -> | |||||
[{port, Port}| _] = ets:lookup(ibrowse_table, port), | |||||
% erl_ddll:unload_driver("ibrowse_drv"), | |||||
% timer:sleep(1000), | |||||
% erl_ddll:load_driver("../priv", "ibrowse_drv"), | |||||
% Port = open_port({spawn, "ibrowse_drv"}, []), | |||||
{Time, Res} = timer:tc(ibrowse_lib, drv_ue, [Data, Port]), | |||||
io:format("Time -> ~p~n", [Time]), | |||||
io:format("Data Length -> ~p~n", [length(Data)]), | |||||
io:format("Res Length -> ~p~n", [length(Res)]). | |||||
% io:format("Result -> ~s~n", [Res]). | |||||
ue_test() -> | |||||
ue_test(lists:duplicate(1024, $?)). | |||||
ue_test(Data) -> | |||||
{Time, Res} = timer:tc(ibrowse_lib, url_encode, [Data]), | |||||
io:format("Time -> ~p~n", [Time]), | |||||
io:format("Data Length -> ~p~n", [length(Data)]), | |||||
io:format("Res Length -> ~p~n", [length(Res)]). | |||||
% io:format("Result -> ~s~n", [Res]). |
@ -0,0 +1,2 @@ | |||||
IBROWSE_VSN = 1.2 | |||||