aboutsummaryrefslogtreecommitdiffstats
path: root/lib/inets
diff options
context:
space:
mode:
Diffstat (limited to 'lib/inets')
-rw-r--r--lib/inets/doc/src/Makefile23
-rw-r--r--lib/inets/doc/src/book.gifbin1081 -> 0 bytes
-rw-r--r--lib/inets/doc/src/fascicules.xml19
-rw-r--r--lib/inets/doc/src/httpc.xml10
-rw-r--r--lib/inets/doc/src/inets.gifbin9763 -> 0 bytes
-rw-r--r--lib/inets/doc/src/min_head.gifbin2652 -> 0 bytes
-rw-r--r--lib/inets/doc/src/note.gifbin1539 -> 0 bytes
-rw-r--r--lib/inets/doc/src/notes.gifbin2005 -> 0 bytes
-rw-r--r--lib/inets/doc/src/notes.xml83
-rw-r--r--lib/inets/doc/src/part_notes.xml40
-rw-r--r--lib/inets/doc/src/part_notes_history.xml35
-rw-r--r--lib/inets/doc/src/ref_man.gifbin1530 -> 0 bytes
-rw-r--r--lib/inets/doc/src/summary.html.src1
-rw-r--r--lib/inets/doc/src/user_guide.gifbin1581 -> 0 bytes
-rw-r--r--lib/inets/doc/src/warning.gifbin1498 -> 0 bytes
-rw-r--r--lib/inets/src/http_client/httpc.erl116
-rw-r--r--lib/inets/src/http_client/httpc_handler.erl18
-rw-r--r--lib/inets/src/http_client/httpc_manager.erl4
-rw-r--r--lib/inets/src/http_client/httpc_response.erl264
-rw-r--r--lib/inets/src/http_lib/http_util.erl32
-rw-r--r--lib/inets/src/http_server/httpd.erl9
-rw-r--r--lib/inets/src/http_server/httpd_esi.erl27
-rw-r--r--lib/inets/src/http_server/httpd_example.erl5
-rw-r--r--lib/inets/src/http_server/httpd_request.erl4
-rw-r--r--lib/inets/src/http_server/httpd_request_handler.erl11
-rw-r--r--lib/inets/src/http_server/httpd_script_env.erl4
-rw-r--r--lib/inets/src/http_server/mod_esi.erl88
-rw-r--r--lib/inets/src/inets_app/inets.appup.src4
-rw-r--r--lib/inets/test/Makefile9
-rw-r--r--lib/inets/test/http_format_SUITE.erl7
-rw-r--r--lib/inets/test/http_test_lib.erl180
-rw-r--r--lib/inets/test/httpc_SUITE.erl150
-rw-r--r--lib/inets/test/httpd_SUITE.erl24
-rw-r--r--lib/inets/test/httpd_basic_SUITE.erl5
-rw-r--r--lib/inets/test/httpd_bench_SUITE.erl846
-rw-r--r--lib/inets/test/httpd_bench_SUITE_data/1M_filebin0 -> 1000000 bytes
-rw-r--r--lib/inets/test/httpd_bench_SUITE_data/1k_filebin0 -> 1000 bytes
-rw-r--r--lib/inets/test/httpd_mod.erl7
-rw-r--r--lib/inets/test/httpd_test_lib.erl73
-rw-r--r--lib/inets/test/inets.spec4
-rw-r--r--lib/inets/test/inets_SUITE.erl2
-rw-r--r--lib/inets/test/inets_bench.spec1
-rw-r--r--lib/inets/test/inets_test_lib.erl38
-rw-r--r--lib/inets/test/make_certs.erl530
-rw-r--r--lib/inets/vsn.mk2
45 files changed, 2305 insertions, 370 deletions
diff --git a/lib/inets/doc/src/Makefile b/lib/inets/doc/src/Makefile
index cb71fbeb9c..cbfa5c9e30 100644
--- a/lib/inets/doc/src/Makefile
+++ b/lib/inets/doc/src/Makefile
@@ -39,6 +39,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
XML_APPLICATION_FILES = ref_man.xml
XML_CHAPTER_FILES = \
+ introduction.xml \
inets_services.xml \
http_client.xml \
http_server.xml \
@@ -58,7 +59,7 @@ XML_REF3_FILES = \
mod_alias.xml \
mod_auth.xml \
mod_esi.xml \
- mod_security.xml
+ mod_security.xml
XML_PART_FILES = \
part.xml
@@ -83,7 +84,7 @@ HTML_FILES = \
$(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
INFO_FILE = ../../info
-EXTRA_FILES = summary.html.src \
+EXTRA_FILES = \
$(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
$(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \
$(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
@@ -95,10 +96,10 @@ HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
# ----------------------------------------------------
-# FLAGS
+# FLAGS
# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
+XML_FLAGS +=
+DVIPS_FLAGS +=
# ----------------------------------------------------
# Targets
@@ -119,25 +120,25 @@ html: gifs $(HTML_REF_MAN_FILE)
clean clean_docs: clean_html clean_man clean_pdf
rm -f errs core *~
-man: $(MAN3_FILES)
+man: $(MAN3_FILES)
gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-debug opt:
+debug opt:
-clean_pdf:
+clean_pdf:
rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
-clean_html:
+clean_html:
rm -rf $(TOP_HTML_FILES) $(HTMLDIR)/*
clean_man:
- rm -f $(MAN3_FILES)
+ rm -f $(MAN3_FILES)
# ----------------------------------------------------
# Release Target
-# ----------------------------------------------------
+# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
release_docs_spec: docs
diff --git a/lib/inets/doc/src/book.gif b/lib/inets/doc/src/book.gif
deleted file mode 100644
index 94b3868792..0000000000
--- a/lib/inets/doc/src/book.gif
+++ /dev/null
Binary files differ
diff --git a/lib/inets/doc/src/fascicules.xml b/lib/inets/doc/src/fascicules.xml
deleted file mode 100644
index c075478967..0000000000
--- a/lib/inets/doc/src/fascicules.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE fascicules SYSTEM "fascicules.dtd">
-
-<fascicules>
- <fascicule file="part" href="part_frame.html" entry="no">
- User's Guide
- </fascicule>
- <fascicule file="ref_man" href="ref_man_frame.html" entry="yes">
- Reference Manual
- </fascicule>
- <fascicule file="part_notes" href="part_notes_frame.html" entry="no">
- Release Notes
- </fascicule>
- <fascicule file="" href="../../../../doc/print.html" entry="no">
- Off-Print
- </fascicule>
-</fascicules>
-
-
diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml
index 29e4b22632..58714328c5 100644
--- a/lib/inets/doc/src/httpc.xml
+++ b/lib/inets/doc/src/httpc.xml
@@ -287,8 +287,7 @@
{autoredirect, boolean()} |
{proxy_auth, {userstring(), passwordstring()}} |
{version, http_version()} |
- {relaxed, boolean()} |
- {url_encode, boolean()}</v>
+ {relaxed, boolean()}</v>
<v>timeout() = integer() >= 0 | infinity</v>
<v>Options = options()</v>
<v>options() = [option()]</v>
@@ -379,13 +378,6 @@
from the HTTP-standard are enabled.</p>
<p>Default is <c>false</c>.</p>
</item>
-
- <tag><c><![CDATA[url_encode]]></c></tag>
- <item>
- <p>Applies Percent-encoding, also known as URL encoding on the
- URL.</p>
- <p>Default is <c>false</c>.</p>
- </item>
</taglist>
<p>Option (<c>option()</c>) details:</p>
diff --git a/lib/inets/doc/src/inets.gif b/lib/inets/doc/src/inets.gif
deleted file mode 100644
index 64968ae68a..0000000000
--- a/lib/inets/doc/src/inets.gif
+++ /dev/null
Binary files differ
diff --git a/lib/inets/doc/src/min_head.gif b/lib/inets/doc/src/min_head.gif
deleted file mode 100644
index 67948a6378..0000000000
--- a/lib/inets/doc/src/min_head.gif
+++ /dev/null
Binary files differ
diff --git a/lib/inets/doc/src/note.gif b/lib/inets/doc/src/note.gif
deleted file mode 100644
index 6fffe30419..0000000000
--- a/lib/inets/doc/src/note.gif
+++ /dev/null
Binary files differ
diff --git a/lib/inets/doc/src/notes.gif b/lib/inets/doc/src/notes.gif
deleted file mode 100644
index e000cca26a..0000000000
--- a/lib/inets/doc/src/notes.gif
+++ /dev/null
Binary files differ
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 10ef84d7cf..70b2811c0e 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -33,7 +33,88 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 6.4.2</title>
+ <section><title>Inets 6.4.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ CGI environment variable CONTENT_LENGTH shall be a string</p>
+ <p>
+ Own Id: OTP-14679</p>
+ </item>
+ <item>
+ <p>
+ In relaxed mode disregard Content-Length header if there
+ is also a Transfer-Encoding header.</p>
+ <p>
+ Own Id: OTP-14727</p>
+ </item>
+ <item>
+ <p>
+ Eliminated race condition, that could cause http request
+ to sporadically fail to complete successfully, when
+ keep-alive connections are used.</p>
+ <p>
+ Own Id: OTP-14783</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 6.4.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Correct the handling of location headers so that the
+ status code is not hard coded. This should have been
+ fixed by commit 2cc5ba70cbbc6b3ace81a2a0324417c3b65265bb
+ but unfortunately was broken during a code refactoring
+ and unnoticed due to a faulty placed test case.</p>
+ <p>
+ Own Id: OTP-14761</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 6.4.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Fix broken handling of POST requests</p>
+ <p>
+ New chunk mechanism of body data in POST requests added
+ in 5d01c70ca399edf28e99dc760506329689fab6ba broke
+ handling of POST body data not using the new mechanism.</p>
+ <p>
+ Own Id: OTP-14656</p>
+ </item>
+ <item>
+ <p>
+ Make sure ints:stop/2 of the service httpd is synchronous</p>
+ <p>
+ Own Id: OTP-14696</p>
+ </item>
+ <item>
+ <p>
+ Honor status code returned by ESI script and modernize
+ "location" header handling.</p>
+ <p>
+ Own Id: OTP-14716</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 6.4.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/inets/doc/src/part_notes.xml b/lib/inets/doc/src/part_notes.xml
deleted file mode 100644
index d10c829f4a..0000000000
--- a/lib/inets/doc/src/part_notes.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE part SYSTEM "part.dtd">
-
-<part xmlns:xi="http://www.w3.org/2001/XInclude">
- <header>
- <copyright>
- <year>2002</year><year>2016</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>Inets</title>
- <prepared>Micael Karlberg</prepared>
- <docno></docno>
- <date>2002-02-28</date>
- <rev>3.0</rev>
- <file>part_notes.sgml</file>
- </header>
- <description>
- <p>A set of services such as a Web server and a ftp client etc. </p>
- <p>For information about older versions see
- <url href="part_notes_history_frame.html">release notes history</url>.</p>
- </description>
- <xi:include file="notes.xml"/>
-</part>
-
-
diff --git a/lib/inets/doc/src/part_notes_history.xml b/lib/inets/doc/src/part_notes_history.xml
deleted file mode 100644
index 66e3307f69..0000000000
--- a/lib/inets/doc/src/part_notes_history.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE part SYSTEM "part.dtd">
-
-<part>
- <header>
- <copyright>
- <year>2004</year><year>2016</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>Inets</title>
- <prepared>Micael Karlberg</prepared>
- <docno></docno>
- <date>2002-02-28</date>
- <rev>3.0</rev>
- <file>part_notes.sgml</file>
- </header>
- <include file="notes_history"></include>
-</part>
-
-
diff --git a/lib/inets/doc/src/ref_man.gif b/lib/inets/doc/src/ref_man.gif
deleted file mode 100644
index b13c4efd53..0000000000
--- a/lib/inets/doc/src/ref_man.gif
+++ /dev/null
Binary files differ
diff --git a/lib/inets/doc/src/summary.html.src b/lib/inets/doc/src/summary.html.src
deleted file mode 100644
index 17637a0787..0000000000
--- a/lib/inets/doc/src/summary.html.src
+++ /dev/null
@@ -1 +0,0 @@
-A set of services such as a web server and a ftp client etc \ No newline at end of file
diff --git a/lib/inets/doc/src/user_guide.gif b/lib/inets/doc/src/user_guide.gif
deleted file mode 100644
index e6275a803d..0000000000
--- a/lib/inets/doc/src/user_guide.gif
+++ /dev/null
Binary files differ
diff --git a/lib/inets/doc/src/warning.gif b/lib/inets/doc/src/warning.gif
deleted file mode 100644
index 96af52360e..0000000000
--- a/lib/inets/doc/src/warning.gif
+++ /dev/null
Binary files differ
diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl
index bf2da82603..821eb7f02f 100644
--- a/lib/inets/src/http_client/httpc.erl
+++ b/lib/inets/src/http_client/httpc.erl
@@ -175,10 +175,10 @@ request(Method,
(Method =:= delete) orelse
(Method =:= trace) andalso
(is_atom(Profile) orelse is_pid(Profile)) ->
- case uri_parse(Url, Options) of
- {error, Reason} ->
+ case uri_string:parse(uri_string:normalize(Url)) of
+ {error, Reason, _} ->
{error, Reason};
- {ok, ParsedUrl} ->
+ ParsedUrl ->
case header_parse(Headers) of
{error, Reason} ->
{error, Reason};
@@ -189,10 +189,10 @@ request(Method,
end.
do_request(Method, {Url, Headers, ContentType, Body}, HTTPOptions, Options, Profile) ->
- case uri_parse(Url, Options) of
- {error, Reason} ->
+ case uri_string:parse(uri_string:normalize(Url)) of
+ {error, Reason, _} ->
{error, Reason};
- {ok, ParsedUrl} ->
+ ParsedUrl ->
handle_request(Method, Url,
ParsedUrl, Headers, ContentType, Body,
HTTPOptions, Options, Profile)
@@ -312,23 +312,28 @@ store_cookies(SetCookieHeaders, Url) ->
store_cookies(SetCookieHeaders, Url, Profile)
when is_atom(Profile) orelse is_pid(Profile) ->
- try
- begin
+ case uri_string:parse(uri_string:normalize(Url)) of
+ {error, Bad, _} ->
+ {error, {parse_failed, Bad}};
+ URI ->
+ Scheme = scheme_to_atom(maps:get(scheme, URI, '')),
+ Host = maps:get(host, URI, ""),
+ Port = maps:get(port, URI, default_port(Scheme)),
+ Path = uri_string:recompose(#{path => maps:get(path, URI, "")}),
%% Since the Address part is not actually used
%% by the manager when storing cookies, we dont
%% care about ipv6-host-with-brackets.
- {ok, {_, _, Host, Port, Path, _}} = uri_parse(Url),
Address = {Host, Port},
ProfileName = profile_name(Profile),
Cookies = httpc_cookie:cookies(SetCookieHeaders, Path, Host),
httpc_manager:store_cookies(Cookies, Address, ProfileName),
ok
- end
- catch
- error:{badmatch, Bad} ->
- {error, {parse_failed, Bad}}
end.
+default_port(http) ->
+ 80;
+default_port(https) ->
+ 443.
%%--------------------------------------------------------------------------
%% cookie_header(Url) -> Header | {error, Reason}
@@ -495,7 +500,7 @@ service_info(Pid) ->
%%% Internal functions
%%%========================================================================
handle_request(Method, Url,
- {Scheme, UserInfo, Host, Port, Path, Query},
+ URI,
Headers0, ContentType, Body0,
HTTPOptions0, Options0, Profile) ->
@@ -520,37 +525,40 @@ handle_request(Method, Url,
throw({error, {bad_body, Body0}})
end,
- HTTPOptions = http_options(HTTPOptions0),
- Options = request_options(Options0),
- Sync = proplists:get_value(sync, Options),
- Stream = proplists:get_value(stream, Options),
- Host2 = http_request:normalize_host(Scheme, Host, Port),
- HeadersRecord = header_record(NewHeaders, Host2, HTTPOptions),
- Receiver = proplists:get_value(receiver, Options),
- SocketOpts = proplists:get_value(socket_opts, Options),
- BracketedHost = proplists:get_value(ipv6_host_with_brackets,
- Options),
- MaybeEscPath = maybe_encode_uri(HTTPOptions, Path),
- MaybeEscQuery = maybe_encode_uri(HTTPOptions, Query),
- AbsUri = maybe_encode_uri(HTTPOptions, Url),
+ HTTPOptions = http_options(HTTPOptions0),
+ Options = request_options(Options0),
+ Sync = proplists:get_value(sync, Options),
+ Stream = proplists:get_value(stream, Options),
+ Receiver = proplists:get_value(receiver, Options),
+ SocketOpts = proplists:get_value(socket_opts, Options),
+ BracketedHost = proplists:get_value(ipv6_host_with_brackets,
+ Options),
+
+ Scheme = scheme_to_atom(maps:get(scheme, URI, '')),
+ Userinfo = maps:get(userinfo, URI, ""),
+ Host = http_util:maybe_add_brackets(maps:get(host, URI, ""), BracketedHost),
+ Port = maps:get(port, URI, default_port(Scheme)),
+ Host2 = http_request:normalize_host(Scheme, Host, Port),
+ Path = uri_string:recompose(#{path => maps:get(path, URI, "")}),
+ Query = add_question_mark(maps:get(query, URI, "")),
+ HeadersRecord = header_record(NewHeaders, Host2, HTTPOptions),
Request = #request{from = Receiver,
- scheme = Scheme,
- address = {host_address(Host, BracketedHost), Port},
- path = MaybeEscPath,
- pquery = MaybeEscQuery,
+ scheme = Scheme,
+ address = {Host, Port},
+ path = Path,
+ pquery = Query,
method = Method,
headers = HeadersRecord,
content = {ContentType, Body},
settings = HTTPOptions,
- abs_uri = AbsUri,
- userinfo = UserInfo,
+ abs_uri = Url,
+ userinfo = Userinfo,
stream = Stream,
headers_as_is = headers_as_is(Headers0, Options),
socket_opts = SocketOpts,
started = Started,
ipv6_host_with_brackets = BracketedHost},
-
case httpc_manager:request(Request, profile_name(Profile)) of
{ok, RequestId} ->
handle_answer(RequestId, Sync, Options);
@@ -565,14 +573,31 @@ handle_request(Method, Url,
Error
end.
+
+add_question_mark(<<>>) ->
+ <<>>;
+add_question_mark([]) ->
+ [];
+add_question_mark(Comp) when is_binary(Comp) ->
+ <<$?, Comp/binary>>;
+add_question_mark(Comp) when is_list(Comp) ->
+ [$?|Comp].
+
+
+scheme_to_atom("http") ->
+ http;
+scheme_to_atom("https") ->
+ https;
+scheme_to_atom('') ->
+ '';
+scheme_to_atom(Scheme) ->
+ throw({error, {bad_scheme, Scheme}}).
+
+
ensure_chunked_encoding(Hdrs) ->
Key = "transfer-encoding",
lists:keystore(Key, 1, Hdrs, {Key, "chunked"}).
-maybe_encode_uri(#http_options{url_encode = true}, URI) ->
- http_uri:encode(URI);
-maybe_encode_uri(_, URI) ->
- URI.
mk_chunkify_fun(ProcessBody) ->
fun(eof_body) ->
@@ -1190,17 +1215,6 @@ validate_headers(RequestHeaders, _, _) ->
%% These functions is just simple wrappers to parse specifically HTTP URIs
%%--------------------------------------------------------------------------
-scheme_defaults() ->
- [{http, 80}, {https, 443}].
-
-uri_parse(URI) ->
- http_uri:parse(URI, [{scheme_defaults, scheme_defaults()}]).
-
-uri_parse(URI, Opts) ->
- http_uri:parse(URI, [{scheme_defaults, scheme_defaults()} | Opts]).
-
-
-%%--------------------------------------------------------------------------
header_parse([]) ->
ok;
header_parse([{Field, Value}|T]) when is_list(Field), is_list(Value) ->
@@ -1221,10 +1235,6 @@ child_name(Pid, [{Name, Pid} | _]) ->
child_name(Pid, [_ | Children]) ->
child_name(Pid, Children).
-host_address(Host, false) ->
- Host;
-host_address(Host, true) ->
- string:strip(string:strip(Host, right, $]), left, $[).
check_body_gen({Fun, _}) when is_function(Fun) ->
ok;
diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl
index bd1d2e833a..1482f4f922 100644
--- a/lib/inets/src/http_client/httpc_handler.erl
+++ b/lib/inets/src/http_client/httpc_handler.erl
@@ -109,7 +109,7 @@ start_link(Parent, Request, Options, ProfileName) ->
%% to be called by the httpc manager process.
%%--------------------------------------------------------------------
send(Request, Pid) ->
- call(Request, Pid, 5000).
+ call(Request, Pid).
%%--------------------------------------------------------------------
@@ -712,12 +712,16 @@ do_handle_info({'EXIT', _, _}, State = #state{request = undefined}) ->
do_handle_info({'EXIT', _, _}, State) ->
{noreply, State#state{status = close}}.
-
call(Msg, Pid) ->
- call(Msg, Pid, infinity).
-
-call(Msg, Pid, Timeout) ->
- gen_server:call(Pid, Msg, Timeout).
+ try gen_server:call(Pid, Msg)
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{normal, _} ->
+ {error, closed};
+ exit:{{shutdown, _},_} ->
+ {error, closed}
+ end.
cast(Msg, Pid) ->
gen_server:cast(Pid, Msg).
@@ -736,7 +740,7 @@ maybe_send_answer(Request, Answer, State) ->
answer_request(Request, Answer, State).
deliver_answer(#request{from = From} = Request)
- when is_pid(From) ->
+ when From =/= answer_sent ->
Response = httpc_response:error(Request, socket_closed_remotely),
httpc_response:send(From, Response);
deliver_answer(_Request) ->
diff --git a/lib/inets/src/http_client/httpc_manager.erl b/lib/inets/src/http_client/httpc_manager.erl
index a63864493f..ffdf1603b3 100644
--- a/lib/inets/src/http_client/httpc_manager.erl
+++ b/lib/inets/src/http_client/httpc_manager.erl
@@ -849,11 +849,11 @@ pipeline_or_keep_alive(#request{id = Id,
from = From} = Request,
HandlerPid,
#state{handler_db = HandlerDb} = State) ->
- case (catch httpc_handler:send(Request, HandlerPid)) of
+ case httpc_handler:send(Request, HandlerPid) of
ok ->
HandlerInfo = {Id, HandlerPid, From},
ets:insert(HandlerDb, HandlerInfo);
- _ -> % timeout pipelining failed
+ {error, closed} -> % timeout pipelining failed
start_handler(Request, State)
end.
diff --git a/lib/inets/src/http_client/httpc_response.erl b/lib/inets/src/http_client/httpc_response.erl
index b3b11b74ab..58ab9144df 100644
--- a/lib/inets/src/http_client/httpc_response.erl
+++ b/lib/inets/src/http_client/httpc_response.erl
@@ -190,7 +190,7 @@ parse_status_code(<<?CR, ?LF, Rest/binary>>, StatusCodeStr,
MaxHeaderSize, Result, true) ->
parse_headers(Rest, [], [], MaxHeaderSize,
[" ", list_to_integer(lists:reverse(
- string:strip(StatusCodeStr)))
+ string:trim(StatusCodeStr)))
| Result], true);
parse_status_code(<<?SP, Rest/binary>>, StatusCodeStr,
@@ -269,7 +269,7 @@ parse_headers(<<?LF,?LF,Body/binary>>, Header, Headers,
MaxHeaderSize, Result, Relaxed);
parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers,
- MaxHeaderSize, Result, _) ->
+ MaxHeaderSize, Result, Relaxed) ->
HTTPHeaders = [lists:reverse(Header) | Headers],
Length = lists:foldl(fun(H, Acc) -> length(H) + Acc end,
0, HTTPHeaders),
@@ -277,8 +277,42 @@ parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers,
true ->
ResponseHeaderRcord =
http_response:headers(HTTPHeaders, #http_response_h{}),
- {ok, list_to_tuple(
- lists:reverse([Body, ResponseHeaderRcord | Result]))};
+
+ %% RFC7230, Section 3.3.3
+ %% If a message is received with both a Transfer-Encoding and a
+ %% Content-Length header field, the Transfer-Encoding overrides the
+ %% Content-Length. Such a message might indicate an attempt to
+ %% perform request smuggling (Section 9.5) or response splitting
+ %% (Section 9.4) and ought to be handled as an error. A sender MUST
+ %% remove the received Content-Length field prior to forwarding such
+ %% a message downstream.
+ case ResponseHeaderRcord#http_response_h.'transfer-encoding' of
+ undefined ->
+ {ok, list_to_tuple(
+ lists:reverse([Body, ResponseHeaderRcord | Result]))};
+ Value ->
+ TransferEncoding = string:lowercase(Value),
+ ContentLength = ResponseHeaderRcord#http_response_h.'content-length',
+ if
+ %% Respond without error but remove Content-Length field in relaxed mode
+ (Relaxed =:= true)
+ andalso (TransferEncoding =:= "chunked")
+ andalso (ContentLength =/= "-1") ->
+ ResponseHeaderRcordFixed =
+ ResponseHeaderRcord#http_response_h{'content-length' = "-1"},
+ {ok, list_to_tuple(
+ lists:reverse([Body, ResponseHeaderRcordFixed | Result]))};
+ %% Respond with error in default (not relaxed) mode
+ (Relaxed =:= false)
+ andalso (TransferEncoding =:= "chunked")
+ andalso (ContentLength =/= "-1") ->
+ throw({error, {headers_conflict, {'content-length',
+ 'transfer-encoding'}}});
+ true ->
+ {ok, list_to_tuple(
+ lists:reverse([Body, ResponseHeaderRcord | Result]))}
+ end
+ end;
false ->
throw({error, {header_too_long, MaxHeaderSize,
MaxHeaderSize-Length}})
@@ -343,58 +377,173 @@ status_server_error_50x(Response, Request) ->
{stop, {Request#request.id, Msg}}.
-redirect(Response = {StatusLine, Headers, Body}, Request) ->
+redirect(Response = {_, Headers, _}, Request) ->
{_, Data} = format_response(Response),
case Headers#http_response_h.location of
- undefined ->
- transparent(Response, Request);
- RedirUrl ->
- UrlParseOpts = [{ipv6_host_with_brackets,
- Request#request.ipv6_host_with_brackets}],
- case uri_parse(RedirUrl, UrlParseOpts) of
- {error, no_scheme} when
- (Request#request.settings)#http_options.relaxed ->
- NewLocation = fix_relative_uri(Request, RedirUrl),
- redirect({StatusLine, Headers#http_response_h{
- location = NewLocation},
- Body}, Request);
- {error, Reason} ->
- {ok, error(Request, Reason), Data};
- %% Automatic redirection
- {ok, {Scheme, _, Host, Port, Path, Query}} ->
- HostPort = http_request:normalize_host(Scheme, Host, Port),
- NewHeaders =
- (Request#request.headers)#http_request_h{host = HostPort},
- NewRequest =
- Request#request{redircount =
- Request#request.redircount+1,
- scheme = Scheme,
- headers = NewHeaders,
- address = {Host,Port},
- path = Path,
- pquery = Query,
- abs_uri =
- atom_to_list(Scheme) ++ "://" ++
- Host ++ ":" ++
- integer_to_list(Port) ++
- Path ++ Query},
- {redirect, NewRequest, Data}
- end
+ undefined ->
+ transparent(Response, Request);
+ RedirUrl ->
+ Brackets = Request#request.ipv6_host_with_brackets,
+ case uri_string:parse(RedirUrl) of
+ {error, Reason, _} ->
+ {ok, error(Request, Reason), Data};
+ %% Automatic redirection
+ URI ->
+ {Host, Port0} = Request#request.address,
+ Port = maybe_to_integer(Port0),
+ Path = Request#request.path,
+ Scheme = atom_to_list(Request#request.scheme),
+ Query = Request#request.pquery,
+ URIMap = resolve_uri(Scheme, Host, Port, Path, Query, URI),
+ TScheme = list_to_atom(maps:get(scheme, URIMap)),
+ THost = http_util:maybe_add_brackets(maps:get(host, URIMap), Brackets),
+ TPort = maps:get(port, URIMap),
+ TPath = maps:get(path, URIMap),
+ TQuery = maps:get(query, URIMap, ""),
+ NewURI = uri_string:normalize(
+ uri_string:recompose(URIMap)),
+ HostPort = http_request:normalize_host(TScheme, THost, TPort),
+ NewHeaders =
+ (Request#request.headers)#http_request_h{host = HostPort},
+ NewRequest =
+ Request#request{redircount =
+ Request#request.redircount+1,
+ scheme = TScheme,
+ headers = NewHeaders,
+ address = {THost,TPort},
+ path = TPath,
+ pquery = TQuery,
+ abs_uri = NewURI},
+ {redirect, NewRequest, Data}
+ end
+ end.
+
+
+%% RFC3986 - 5.2.2. Transform References
+resolve_uri(Scheme, Host, Port, Path, Query, URI) ->
+ resolve_uri(Scheme, Host, Port, Path, Query, URI, #{}).
+%%
+resolve_uri(Scheme, Host, Port, Path, Query, URI, Map0) ->
+ case maps:is_key(scheme, URI) of
+ true ->
+ Port = get_port(URI),
+ maybe_add_query(
+ Map0#{scheme => maps:get(scheme, URI),
+ host => maps:get(host, URI),
+ port => Port,
+ path => maps:get(path, URI)},
+ URI);
+ false ->
+ Map = Map0#{scheme => Scheme},
+ resolve_authority(Host, Port, Path, Query, URI, Map)
+ end.
+
+
+get_port(URI) ->
+ Scheme = maps:get(scheme, URI),
+ case maps:get(port, URI, undefined) of
+ undefined ->
+ get_default_port(Scheme);
+ Port ->
+ Port
+ end.
+
+
+get_default_port("http") ->
+ 80;
+get_default_port("https") ->
+ 443.
+
+
+resolve_authority(Host, Port, Path, Query, RelURI, Map) ->
+ case maps:is_key(host, RelURI) of
+ true ->
+ Port = get_port(RelURI),
+ maybe_add_query(
+ Map#{host => maps:get(host, RelURI),
+ port => Port,
+ path => maps:get(path, RelURI)},
+ RelURI);
+ false ->
+ Map1 = Map#{host => Host,
+ port => Port},
+ resolve_path(Path, Query, RelURI, Map1)
+ end.
+
+
+maybe_add_query(Map, RelURI) ->
+ case maps:is_key(query, RelURI) of
+ true ->
+ Map#{query => maps:get(query, RelURI)};
+ false ->
+ Map
+ end.
+
+
+resolve_path(Path, Query, RelURI, Map) ->
+ case maps:is_key(path, RelURI) of
+ true ->
+ Path1 = calculate_path(Path, maps:get(path, RelURI)),
+ maybe_add_query(
+ Map#{path => Path1},
+ RelURI);
+ false ->
+ Map1 = Map#{path => Path},
+ resolve_query(Query, RelURI, Map1)
+ end.
+
+
+calculate_path(BaseP, RelP) ->
+ case starts_with_slash(RelP) of
+ true ->
+ RelP;
+ false ->
+ merge_paths(BaseP, RelP)
end.
-maybe_to_list(Port) when is_integer(Port) ->
- integer_to_list(Port);
-maybe_to_list(Port) when is_list(Port) ->
+
+starts_with_slash([$/|_]) ->
+ true;
+starts_with_slash(<<$/,_/binary>>) ->
+ true;
+starts_with_slash(_) ->
+ false.
+
+
+%% RFC3986 - 5.2.3. Merge Paths
+merge_paths("", RelP) ->
+ [$/|RelP];
+merge_paths(BaseP, RelP) when is_list(BaseP) ->
+ do_merge_paths(lists:reverse(BaseP), RelP);
+merge_paths(BaseP, RelP) when is_binary(BaseP) ->
+ B = binary_to_list(BaseP),
+ R = binary_to_list(RelP),
+ Res = merge_paths(B, R),
+ list_to_binary(Res).
+
+
+do_merge_paths([$/|_] = L, RelP) ->
+ lists:reverse(L) ++ RelP;
+do_merge_paths([_|T], RelP) ->
+ do_merge_paths(T, RelP).
+
+
+resolve_query(Query, RelURI, Map) ->
+ case maps:is_key(query, RelURI) of
+ true ->
+ Map#{query => maps:get(query, RelURI)};
+ false ->
+ Map#{query => Query}
+ end.
+
+
+maybe_to_integer(Port) when is_list(Port) ->
+ {Port1, _} = string:to_integer(Port),
+ Port1;
+maybe_to_integer(Port) when is_integer(Port) ->
Port.
-%%% Guessing that we received a relative URI, fix it to become an absoluteURI
-fix_relative_uri(Request, RedirUrl) ->
- {Server, Port0} = Request#request.address,
- Port = maybe_to_list(Port0),
- Path = Request#request.path,
- atom_to_list(Request#request.scheme) ++ "://" ++ Server ++ ":" ++ Port
- ++ Path ++ RedirUrl.
-
+
error(#request{id = Id}, Reason) ->
{Id, {error, Reason}}.
@@ -444,18 +593,3 @@ format_response({StatusLine, Headers, Body}) ->
{Body, <<>>}
end,
{{StatusLine, http_response:header_list(Headers), NewBody}, Data}.
-
-%%--------------------------------------------------------------------------
-%% These functions is just simple wrappers to parse specifically HTTP URIs
-%%--------------------------------------------------------------------------
-
-scheme_defaults() ->
- [{http, 80}, {https, 443}].
-
-uri_parse(URI, Opts) ->
- http_uri:parse(URI, [{scheme_defaults, scheme_defaults()} | Opts]).
-
-
-%%--------------------------------------------------------------------------
-
-
diff --git a/lib/inets/src/http_lib/http_util.erl b/lib/inets/src/http_lib/http_util.erl
index 487d04f7aa..5577b00cc8 100644
--- a/lib/inets/src/http_lib/http_util.erl
+++ b/lib/inets/src/http_lib/http_util.erl
@@ -27,7 +27,8 @@
convert_month/1,
is_hostname/1,
timestamp/0, timeout/2,
- html_encode/1
+ html_encode/1,
+ maybe_add_brackets/2
]).
@@ -194,6 +195,24 @@ html_encode(Chars) ->
lists:append([char_to_html_entity(Char, Reserved) || Char <- Chars]).
+maybe_add_brackets(Addr, false) ->
+ Addr;
+maybe_add_brackets(Addr, true) when is_list(Addr) ->
+ case is_ipv6_address(Addr) of
+ true ->
+ [$[|Addr] ++ "]";
+ false ->
+ Addr
+ end;
+maybe_add_brackets(Addr, true) when is_binary(Addr) ->
+ case is_ipv6_address(Addr) of
+ true ->
+ <<$[,Addr/binary,$]>>;
+ false ->
+ Addr
+ end.
+
+
%%%========================================================================
%%% Internal functions
%%%========================================================================
@@ -205,3 +224,14 @@ char_to_html_entity(Char, Reserved) ->
false ->
[Char]
end.
+
+is_ipv6_address(Addr) when is_binary(Addr) ->
+ B = binary_to_list(Addr),
+ is_ipv6_address(B);
+is_ipv6_address(Addr) when is_list(Addr) ->
+ case inet:parse_ipv6strict_address(Addr) of
+ {ok, _ } ->
+ true;
+ {error, _} ->
+ false
+ end.
diff --git a/lib/inets/src/http_server/httpd.erl b/lib/inets/src/http_server/httpd.erl
index 0b632d24e3..540e68e749 100644
--- a/lib/inets/src/http_server/httpd.erl
+++ b/lib/inets/src/http_server/httpd.erl
@@ -99,7 +99,14 @@ start_service(Conf) ->
stop_service({Address, Port}) ->
stop_service({Address, Port, ?DEFAULT_PROFILE});
stop_service({Address, Port, Profile}) ->
- httpd_sup:stop_child(Address, Port, Profile);
+ Name = httpd_util:make_name("httpd_instance_sup", Address, Port, Profile),
+ Pid = whereis(Name),
+ MonitorRef = erlang:monitor(process, Pid),
+ Result = httpd_sup:stop_child(Address, Port, Profile),
+ receive
+ {'DOWN', MonitorRef, _, _, _} ->
+ Result
+ end;
stop_service(Pid) when is_pid(Pid) ->
case service_info(Pid) of
{ok, Info} ->
diff --git a/lib/inets/src/http_server/httpd_esi.erl b/lib/inets/src/http_server/httpd_esi.erl
index 9406b47802..f5493f6fad 100644
--- a/lib/inets/src/http_server/httpd_esi.erl
+++ b/lib/inets/src/http_server/httpd_esi.erl
@@ -66,7 +66,7 @@ handle_headers("") ->
{ok, [], 200};
handle_headers(Headers) ->
NewHeaders = string:tokens(Headers, ?CRLF),
- handle_headers(NewHeaders, [], 200).
+ handle_headers(NewHeaders, [], 200, true).
%%%========================================================================
%%% Internal functions
@@ -80,21 +80,17 @@ parse_headers([?CR, ?LF, ?CR, ?LF | Rest], Acc) ->
parse_headers([Char | Rest], Acc) ->
parse_headers(Rest, [Char | Acc]).
-handle_headers([], NewHeaders, StatusCode) ->
+handle_headers([], NewHeaders, StatusCode, _) ->
{ok, NewHeaders, StatusCode};
-handle_headers([Header | Headers], NewHeaders, StatusCode) ->
+handle_headers([Header | Headers], NewHeaders, StatusCode, NoESIStatus) ->
{FieldName, FieldValue} = httpd_response:split_header(Header, []),
case FieldName of
- "location" ->
- case http_request:is_absolut_uri(FieldValue) of
- true ->
- handle_headers(Headers,
- [{FieldName, FieldValue} | NewHeaders],
- 302);
- false ->
- {proceed, FieldValue}
- end;
+ "location" when NoESIStatus == true ->
+ handle_headers(Headers,
+ [{FieldName, FieldValue} | NewHeaders],
+ 302, NoESIStatus);
+
"status" ->
NewStatusCode =
case httpd_util:split(FieldValue," ",2) of
@@ -103,8 +99,9 @@ handle_headers([Header | Headers], NewHeaders, StatusCode) ->
_ ->
200
end,
- handle_headers(Headers, NewHeaders, NewStatusCode);
+ handle_headers(Headers, NewHeaders, NewStatusCode, false);
_ ->
handle_headers(Headers,
- [{FieldName, FieldValue}| NewHeaders], StatusCode)
- end.
+ [{FieldName, FieldValue}| NewHeaders], StatusCode,
+ NoESIStatus)
+ end.
diff --git a/lib/inets/src/http_server/httpd_example.erl b/lib/inets/src/http_server/httpd_example.erl
index 45b6deba97..47a8c48d01 100644
--- a/lib/inets/src/http_server/httpd_example.erl
+++ b/lib/inets/src/http_server/httpd_example.erl
@@ -20,7 +20,7 @@
%%
-module(httpd_example).
-export([print/1]).
--export([get/2, put/2, post/2, yahoo/2, test1/2, get_bin/2, peer/2]).
+-export([get/2, put/2, post/2, yahoo/2, test1/2, get_bin/2, peer/2,new_status_and_location/2]).
-export([newformat/3, post_chunked/3]).
%% These are used by the inets test-suite
@@ -90,6 +90,9 @@ post(Env,Input) ->
yahoo(_Env,_Input) ->
"Location: http://www.yahoo.com\r\n\r\n".
+new_status_and_location(_Env,_Input) ->
+ "status:201 Created\r\n Location: http://www.yahoo.com\r\n\r\n".
+
default(Env,Input) ->
[header(),
top("Default Example"),
diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl
index 0eaf073255..007d272323 100644
--- a/lib/inets/src/http_server/httpd_request.erl
+++ b/lib/inets/src/http_server/httpd_request.erl
@@ -306,10 +306,10 @@ add_chunk([<<>>, Body, Length, MaxChunk]) ->
add_chunk([More, Body, Length, MaxChunk]) ->
body_chunk(<<Body/binary, More/binary>>, Length, MaxChunk).
-body_chunk(<<>> = Body, Length, MaxChunk) ->
- {ok, {continue, ?MODULE, add_chunk, [Body, Length, MaxChunk]}};
body_chunk(Body, Length, nolimit) ->
whole_body(Body, Length);
+body_chunk(<<>> = Body, Length, MaxChunk) ->
+ {ok, {continue, ?MODULE, add_chunk, [Body, Length, MaxChunk]}};
body_chunk(Body, Length, MaxChunk) when Length > MaxChunk ->
case size(Body) >= MaxChunk of
diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl
index bd4fdd3832..d918f10424 100644
--- a/lib/inets/src/http_server/httpd_request_handler.erl
+++ b/lib/inets/src/http_server/httpd_request_handler.erl
@@ -516,6 +516,15 @@ handle_body(#state{headers = Headers, body = Body,
case ((Length =< MaxBodySize) or (MaxBodySize == nolimit)) of
true ->
case httpd_request:body_chunk_first(Body, Length, MaxChunk) of
+ %% This is the case that the we need more data to complete
+ %% the body but chunking to the mod_esi user is not enabled.
+ {Module, add_chunk = Function, Args} ->
+ http_transport:setopts(ModData#mod.socket_type,
+ ModData#mod.socket,
+ [{active, once}]),
+ {noreply, State#state{mfa =
+ {Module, Function, Args}}};
+ %% Chunking to mod_esi user is enabled
{ok, {continue, Module, Function, Args}} ->
http_transport:setopts(ModData#mod.socket_type,
ModData#mod.socket,
@@ -525,6 +534,8 @@ handle_body(#state{headers = Headers, body = Body,
{ok, {{continue, Chunk}, Module, Function, Args}} ->
handle_internal_chunk(State#state{chunk = chunk_start(MaxChunk),
body = Chunk}, Module, Function, Args);
+ %% Whole body delivered, if chunking mechanism is enabled the whole
+ %% body fits in one chunk.
{ok, NewBody} ->
handle_response(State#state{chunk = chunk_finish(ChunkState,
CbState, MaxChunk),
diff --git a/lib/inets/src/http_server/httpd_script_env.erl b/lib/inets/src/http_server/httpd_script_env.erl
index 055f08fdb0..d7c92c59ef 100644
--- a/lib/inets/src/http_server/httpd_script_env.erl
+++ b/lib/inets/src/http_server/httpd_script_env.erl
@@ -166,9 +166,9 @@ create_script_elements(cgi, path_info, PathInfo, ModData) ->
[{"PATH_INFO", PathInfo},
{"PATH_TRANSLATED", PathTranslated}];
create_script_elements(esi, entity_body, Body, _) ->
- [{content_length, httpd_util:flatlength(Body)}];
+ [{content_length, integer_to_list(httpd_util:flatlength(Body))}];
create_script_elements(cgi, entity_body, Body, _) ->
- [{"CONTENT_LENGTH", httpd_util:flatlength(Body)}];
+ [{"CONTENT_LENGTH", integer_to_list(httpd_util:flatlength(Body))}];
create_script_elements(_, _, _, _) ->
[].
diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl
index 3a589ca5f0..3206d957d9 100644
--- a/lib/inets/src/http_server/mod_esi.erl
+++ b/lib/inets/src/http_server/mod_esi.erl
@@ -339,26 +339,21 @@ erl_scheme_webpage_whole(Mod, Func, Env, Input, ModData) ->
{Headers, Body} =
httpd_esi:parse_headers(lists:flatten(Response)),
Length = httpd_util:flatlength(Body),
- case httpd_esi:handle_headers(Headers) of
- {proceed, AbsPath} ->
- {proceed, [{real_name, httpd_util:split_path(AbsPath)}
- | ModData#mod.data]};
- {ok, NewHeaders, StatusCode} ->
- send_headers(ModData, StatusCode,
- [{"content-length",
- integer_to_list(Length)}| NewHeaders]),
- case ModData#mod.method of
- "HEAD" ->
- {proceed, [{response, {already_sent, 200, 0}} |
- ModData#mod.data]};
- _ ->
- httpd_response:send_body(ModData,
- StatusCode, Body),
- {proceed, [{response, {already_sent, 200,
- Length}} |
- ModData#mod.data]}
- end
- end
+ {ok, NewHeaders, StatusCode} = httpd_esi:handle_headers(Headers),
+ send_headers(ModData, StatusCode,
+ [{"content-length",
+ integer_to_list(Length)}| NewHeaders]),
+ case ModData#mod.method of
+ "HEAD" ->
+ {proceed, [{response, {already_sent, 200, 0}} |
+ ModData#mod.data]};
+ _ ->
+ httpd_response:send_body(ModData,
+ StatusCode, Body),
+ {proceed, [{response, {already_sent, 200,
+ Length}} |
+ ModData#mod.data]}
+ end
end.
%% New API that allows the dynamic wepage to be sent back to the client
@@ -398,29 +393,23 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) ->
{continue, _} = Continue ->
Continue;
{Headers, Body} ->
- case httpd_esi:handle_headers(Headers) of
- {proceed, AbsPath} ->
- {proceed, [{real_name, httpd_util:split_path(AbsPath)}
- | ModData#mod.data]};
- {ok, NewHeaders, StatusCode} ->
- IsDisableChunkedSend =
- httpd_response:is_disable_chunked_send(Db),
- case (ModData#mod.http_version =/= "HTTP/1.1") or
- (IsDisableChunkedSend) of
- true ->
- send_headers(ModData, StatusCode,
- [{"connection", "close"} |
- NewHeaders]);
- false ->
- send_headers(ModData, StatusCode,
- [{"transfer-encoding",
- "chunked"} | NewHeaders])
- end,
- handle_body(Pid, ModData, Body, Timeout, length(Body),
- IsDisableChunkedSend)
- end;
- timeout ->
- send_headers(ModData, 504, [{"connection", "close"}]),
+ {ok, NewHeaders, StatusCode} = httpd_esi:handle_headers(Headers),
+ IsDisableChunkedSend = httpd_response:is_disable_chunked_send(Db),
+ case (ModData#mod.http_version =/= "HTTP/1.1") or
+ (IsDisableChunkedSend) of
+ true ->
+ send_headers(ModData, StatusCode,
+ [{"connection", "close"} |
+ NewHeaders]);
+ false ->
+ send_headers(ModData, StatusCode,
+ [{"transfer-encoding",
+ "chunked"} | NewHeaders])
+ end,
+ handle_body(Pid, ModData, Body, Timeout, length(Body),
+ IsDisableChunkedSend);
+ timeout ->
+ send_headers(ModData, 504, [{"connection", "close"}]),
httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket),
{proceed,[{response, {already_sent, 200, 0}} | ModData#mod.data]}
end.
@@ -560,15 +549,10 @@ eval(#mod{method = Method} = ModData, ESIBody, Modules)
{ok, Response} ->
{Headers, _} =
httpd_esi:parse_headers(lists:flatten(Response)),
- case httpd_esi:handle_headers(Headers) of
- {ok, _, StatusCode} ->
- {proceed,[{response, {StatusCode, Response}} |
- ModData#mod.data]};
- {proceed, AbsPath} ->
- {proceed, [{real_name, AbsPath} |
- ModData#mod.data]}
- end
- end;
+ {ok, _, StatusCode} =httpd_esi:handle_headers(Headers),
+ {proceed,[{response, {StatusCode, Response}} |
+ ModData#mod.data]}
+ end;
false ->
{proceed,[{status,
{403, ModData#mod.request_uri,
diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src
index a86413147c..fdf4cc6e07 100644
--- a/lib/inets/src/inets_app/inets.appup.src
+++ b/lib/inets/src/inets_app/inets.appup.src
@@ -18,10 +18,14 @@
%% %CopyrightEnd%
{"%VSN%",
[
+ {<<"6.4.3">>, [{load_module, httpd_esi,
+ soft_purge, soft_purge, []}]},
{<<"6\\..*">>,[{restart_application, inets}]},
{<<"5\\..*">>,[{restart_application, inets}]}
],
[
+ {<<"6.4.3">>, [{load_module, httpd_esi,
+ soft_purge, soft_purge, []}]},
{<<"6\\..*">>,[{restart_application, inets}]},
{<<"5\\..*">>,[{restart_application, inets}]}
]
diff --git a/lib/inets/test/Makefile b/lib/inets/test/Makefile
index ffc512050a..95e0559470 100644
--- a/lib/inets/test/Makefile
+++ b/lib/inets/test/Makefile
@@ -150,6 +150,7 @@ INETS_ROOT = ../../inets
MODULES = \
inets_test_lib \
erl_make_certs \
+ make_certs \
ftp_SUITE \
ftp_format_SUITE \
http_format_SUITE \
@@ -157,6 +158,8 @@ MODULES = \
httpc_cookie_SUITE \
httpc_proxy_SUITE \
httpd_SUITE \
+ httpd_bench_SUITE \
+ http_test_lib \
old_httpd_SUITE \
httpd_basic_SUITE \
httpd_mod \
@@ -189,7 +192,7 @@ SOURCE = $(ERL_FILES) $(HRL_FILES)
TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
-INETS_SPECS = inets.spec
+INETS_SPECS = inets.spec inets_bench.spec
COVER_FILE = inets.cover
INETS_FILES = inets.config $(INETS_SPECS)
@@ -200,8 +203,10 @@ INETS_FILES = inets.config $(INETS_SPECS)
# inets_ftp_suite \
# inets_tftp_suite
+
INETS_DATADIRS = inets_SUITE_data inets_socketwrap_SUITE_data
-HTTPD_DATADIRS = httpd_test_data httpd_SUITE_data httpd_basic_SUITE_data old_httpd_SUITE_data
+HTTPD_DATADIRS = httpd_test_data httpd_SUITE_data httpd_basic_SUITE_data old_httpd_SUITE_data httpd_bench_SUITE_data
+
HTTPC_DATADIRS = httpc_SUITE_data httpc_proxy_SUITE_data
FTP_DATADIRS = ftp_SUITE_data
diff --git a/lib/inets/test/http_format_SUITE.erl b/lib/inets/test/http_format_SUITE.erl
index 4e10a97f58..647eff4f7c 100644
--- a/lib/inets/test/http_format_SUITE.erl
+++ b/lib/inets/test/http_format_SUITE.erl
@@ -535,8 +535,11 @@ esi_parse_headers(Config) when is_list(Config) ->
{"location","http://foo.bar.se"}], 302} =
httpd_esi:handle_headers(Headers2),
- {proceed,"/foo/bar.html"} =
- httpd_esi:handle_headers("location:/foo/bar.html\r\n").
+ {ok,[{"location","/foo/bar.html"}], 302} =
+ httpd_esi:handle_headers("location:/foo/bar.html\r\n"),
+
+ {ok,[{"location","http://foo/bar.html"}],201} =
+ httpd_esi:handle_headers("status:201 Created\r\nlocation:http://foo/bar.html\r\n").
%%--------------------------------------------------------------------
cgi_parse_headers() ->
diff --git a/lib/inets/test/http_test_lib.erl b/lib/inets/test/http_test_lib.erl
new file mode 100644
index 0000000000..38e9e4976e
--- /dev/null
+++ b/lib/inets/test/http_test_lib.erl
@@ -0,0 +1,180 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2015-2015. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+-module(http_test_lib).
+
+-include_lib("common_test/include/ct.hrl").
+-include("inets_test_lib.hrl").
+-include("http_internal.hrl").
+-include("httpc_internal.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+dummy_server(SocketType, Inet, Extra) ->
+ dummy_server(self(), SocketType, Inet, Extra).
+
+dummy_server(Caller, SocketType, Inet, Extra) ->
+ Args = [Caller, SocketType, Inet, Extra],
+ Pid = spawn(?MODULE, dummy_server_init, Args),
+ receive
+ {port, Port} ->
+ {Pid, Port}
+ end.
+
+dummy_server_init(Caller, ip_comm, Inet, Extra) ->
+ ContentCb = proplists:get_value(content_cb, Extra),
+ BaseOpts = [binary, {packet, 0}, {reuseaddr,true}, {active, false}, {nodelay, true}],
+ Conf = proplists:get_value(conf, Extra),
+ {ok, ListenSocket} = gen_tcp:listen(0, [Inet | BaseOpts]),
+ {ok, Port} = inet:port(ListenSocket),
+ Caller ! {port, Port},
+ dummy_ipcomm_server_loop({httpd_request, parse, [[{max_uri, ?HTTP_MAX_URI_SIZE},
+ {max_header, ?HTTP_MAX_HEADER_SIZE},
+ {max_version,?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, ?HTTP_MAX_CONTENT_LENGTH},
+ {customize, httpd_custom}
+ ]]},
+ [], ContentCb, Conf, ListenSocket);
+
+dummy_server_init(Caller, ssl, Inet, Extra) ->
+ ContentCb = proplists:get_value(content_cb, Extra),
+ SSLOptions = proplists:get_value(ssl, Extra),
+ Conf = proplists:get_value(conf, Extra),
+ BaseOpts = [binary, {reuseaddr,true}, {active, false}, {nodelay, true} |
+ SSLOptions],
+ dummy_ssl_server_init(Caller, BaseOpts, Inet, ContentCb, Conf).
+
+dummy_ssl_server_init(Caller, BaseOpts, Inet, ContentCb, Conf) ->
+ {ok, ListenSocket} = ssl:listen(0, [Inet | BaseOpts]),
+ {ok, {_, Port}} = ssl:sockname(ListenSocket),
+ Caller ! {port, Port},
+ dummy_ssl_server_loop({httpd_request, parse, [[{max_uri, ?HTTP_MAX_URI_SIZE},
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_version,?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, ?HTTP_MAX_CONTENT_LENGTH},
+ {customize, httpd_custom}
+ ]]},
+ [], ContentCb, Conf, ListenSocket).
+
+dummy_ipcomm_server_loop(MFA, Handlers, ContentCb, Conf, ListenSocket) ->
+ receive
+ stop ->
+ lists:foreach(fun(Handler) -> Handler ! stop end, Handlers);
+ {stop, From} ->
+ Stopper = fun(Handler) -> Handler ! stop end,
+ lists:foreach(Stopper, Handlers),
+ From ! {stopped, self()}
+ after 0 ->
+ {ok, Socket} = gen_tcp:accept(ListenSocket),
+ HandlerPid = dummy_request_handler(MFA, Socket, ContentCb, Conf),
+ gen_tcp:controlling_process(Socket, HandlerPid),
+ HandlerPid ! ipcomm_controller,
+ dummy_ipcomm_server_loop(MFA, [HandlerPid | Handlers],
+ ContentCb, Conf, ListenSocket)
+ end.
+
+dummy_ssl_server_loop(MFA, Handlers, ContentCb, Conf, ListenSocket) ->
+ receive
+ stop ->
+ lists:foreach(fun(Handler) -> Handler ! stop end, Handlers);
+ {stop, From} ->
+ Stopper = fun(Handler) -> Handler ! stop end,
+ lists:foreach(Stopper, Handlers),
+ From ! {stopped, self()}
+ after 0 ->
+ {ok, Socket} = ssl:transport_accept(ListenSocket),
+ HandlerPid = dummy_request_handler(MFA, Socket, ContentCb, Conf),
+ ssl:controlling_process(Socket, HandlerPid),
+ HandlerPid ! ssl_controller,
+ dummy_ssl_server_loop(MFA, [HandlerPid | Handlers],
+ ContentCb, Conf, ListenSocket)
+ end.
+
+dummy_request_handler(MFA, Socket, ContentCb, Conf) ->
+ spawn(?MODULE, dummy_request_handler_init, [MFA, Socket, ContentCb, Conf]).
+
+dummy_request_handler_init(MFA, Socket, ContentCb, Conf) ->
+ SockType =
+ receive
+ ipcomm_controller ->
+ inet:setopts(Socket, [{active, true}]),
+ ip_comm;
+ ssl_controller ->
+ ok = ssl:ssl_accept(Socket, infinity),
+ ssl:setopts(Socket, [{active, true}]),
+ ssl
+ end,
+ dummy_request_handler_loop(MFA, SockType, Socket, ContentCb, Conf).
+
+dummy_request_handler_loop({Module, Function, Args}, SockType, Socket, ContentCb, Conf) ->
+ receive
+ {Proto, _, Data} when (Proto =:= tcp) orelse (Proto =:= ssl) ->
+ case handle_request(Module, Function, [Data | Args], Socket, ContentCb, Conf) of
+ stop when Proto =:= tcp ->
+ gen_tcp:close(Socket);
+ stop when Proto =:= ssl ->
+ ssl:close(Socket);
+ NewMFA ->
+ dummy_request_handler_loop(NewMFA, SockType, Socket, ContentCb, Conf)
+ end;
+ stop when SockType =:= ip_comm ->
+ gen_tcp:close(Socket);
+ stop when SockType =:= ssl ->
+ ssl:close(Socket)
+ end.
+
+handle_request(Module, Function, Args, Socket, ContentCb, Conf) ->
+ case Module:Function(Args) of
+ {ok, Result} ->
+ case ContentCb:handle_http_msg(Result, Socket, Conf) of
+ stop ->
+ stop;
+ <<>> ->
+ {httpd_request, parse, [[{max_uri,?HTTP_MAX_URI_SIZE},
+ {max_header, ?HTTP_MAX_HEADER_SIZE},
+ {max_version,?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, ?HTTP_MAX_CONTENT_LENGTH},
+ {customize, httpd_custom}
+ ]]};
+ Data ->
+ handle_request(httpd_request, parse,
+ [Data, [{max_uri, ?HTTP_MAX_URI_SIZE},
+ {max_header, ?HTTP_MAX_HEADER_SIZE},
+ {max_version,?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, ?HTTP_MAX_CONTENT_LENGTH},
+ {customize, httpd_custom}
+ ]], Socket, ContentCb, Conf)
+ end;
+ NewMFA ->
+ NewMFA
+ end.
+
+%% Perform a synchronous stop
+dummy_server_stop(Pid) ->
+ Pid ! {stop, self()},
+ receive
+ {stopped, Pid} ->
+ ok
+ end.
diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl
index e6dcd2285f..9b5317c6bb 100644
--- a/lib/inets/test/httpc_SUITE.erl
+++ b/lib/inets/test/httpc_SUITE.erl
@@ -42,7 +42,8 @@
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
suite() ->
- [{ct_hooks,[ts_install_cth]}
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{seconds, 30}}
].
all() ->
@@ -57,7 +58,10 @@ all() ->
groups() ->
[
{http, [], real_requests()},
- {sim_http, [], only_simulated()},
+ %% process_leak_on_keepalive is depending on stream_fun_server_close
+ %% and it shall be the last test case in the suite otherwise cookie
+ %% will fail.
+ {sim_http, [], only_simulated() ++ [process_leak_on_keepalive]},
{https, [], real_requests()},
{sim_https, [], only_simulated()},
{misc, [], misc()}
@@ -67,6 +71,7 @@ real_requests()->
[
head,
get,
+ get_query_string,
post,
delete,
post_stream,
@@ -114,10 +119,10 @@ only_simulated() ->
invalid_chunk_size,
headers_dummy,
headers_with_obs_fold,
+ headers_conflict_chunked_with_length,
empty_response_header,
remote_socket_close,
remote_socket_close_async,
- process_leak_on_keepalive,
transfer_encoding,
transfer_encoding_identity,
redirect_loop,
@@ -126,10 +131,13 @@ only_simulated() ->
redirect_found,
redirect_see_other,
redirect_temporary_redirect,
+ redirect_relative_uri,
port_in_host_header,
redirect_port_in_host_header,
relaxed,
- multipart_chunks
+ multipart_chunks,
+ get_space,
+ stream_fun_server_close
].
misc() ->
@@ -142,7 +150,6 @@ misc() ->
%%--------------------------------------------------------------------
init_per_suite(Config) ->
- ct:timetrap({seconds, 30}),
PrivDir = proplists:get_value(priv_dir, Config),
DataDir = proplists:get_value(data_dir, Config),
inets_test_lib:start_apps([inets]),
@@ -169,7 +176,6 @@ init_per_group(Group, Config0) when Group =:= sim_https; Group =:= https->
catch crypto:stop(),
try crypto:start() of
ok ->
- ct:timetrap({seconds, 30}),
start_apps(Group),
do_init_per_group(Group, Config0)
catch
@@ -243,6 +249,25 @@ get(Config) when is_list(Config) ->
{ok, {{_,200,_}, [_ | _], BinBody}} = httpc:request(get, Request, [], [{body_format, binary}]),
true = is_binary(BinBody).
+
+
+get_query_string() ->
+ [{doc, "Test http get request with query string against local server"}].
+get_query_string(Config) when is_list(Config) ->
+ Request = {url(group_name(Config), "/dummy.html?foo=bar", Config), []},
+ {ok, {{_,200,_}, [_ | _], Body = [_ | _]}} = httpc:request(get, Request, [], []),
+
+ inets_test_lib:check_body(Body).
+
+%%--------------------------------------------------------------------
+get_space() ->
+ [{"Test http get request with '%20' in the path of the URL."}].
+get_space(Config) when is_list(Config) ->
+ Request = {url(group_name(Config), "/space%20.html", Config), []},
+ {ok, {{_,200,_}, [_ | _], Body = [_ | _]}} = httpc:request(get, Request, [], []),
+
+ inets_test_lib:check_body(Body).
+
%%--------------------------------------------------------------------
post() ->
[{"Test http post request against local server. We do in this case "
@@ -576,7 +601,26 @@ redirect_temporary_redirect(Config) when is_list(Config) ->
{ok, {{_,200,_}, [_ | _], [_|_]}}
= httpc:request(post, {URL307, [],"text/plain", "foobar"},
[], []).
+%%-------------------------------------------------------------------------
+redirect_relative_uri() ->
+ [{doc, "The server SHOULD generate a Location header field in the response "
+ "containing a preferred URI reference for the new permanent URI. The user "
+ "agent MAY use the Location field value for automatic redirection. The server's "
+ "response payload usually contains a short hypertext note with a "
+ "hyperlink to the new URI(s)."}].
+redirect_relative_uri(Config) when is_list(Config) ->
+
+ URL301 = url(group_name(Config), "/301_rel_uri.html", Config),
+
+ {ok, {{_,200,_}, [_ | _], [_|_]}}
+ = httpc:request(get, {URL301, []}, [], []),
+
+ {ok, {{_,200,_}, [_ | _], []}}
+ = httpc:request(head, {URL301, []}, [], []),
+ {ok, {{_,200,_}, [_ | _], [_|_]}}
+ = httpc:request(post, {URL301, [],"text/plain", "foobar"},
+ [], []).
%%-------------------------------------------------------------------------
redirect_loop() ->
[{"doc, Test redirect loop detection"}].
@@ -746,7 +790,7 @@ empty_body() ->
empty_body(Config) when is_list(Config) ->
URL = url(group_name(Config), "/empty.html", Config),
{ok, {{_,200,_}, [_ | _], []}} =
- httpc:request(get, {URL, []}, [{timeout, 500}], []).
+ httpc:request(get, {URL, []}, [], []).
%%-------------------------------------------------------------------------
@@ -978,7 +1022,6 @@ headers_dummy(Config) when is_list(Config) ->
{"If-Range", "Sat, 29 Oct 1994 19:43:31 GMT"},
{"If-Match", "*"},
{"Content-Type", "text/plain"},
- {"Content-Encoding", "chunked"},
{"Content-Length", "6"},
{"Content-Language", "en"},
{"Content-Location", "http://www.foobar.se"},
@@ -1004,6 +1047,18 @@ headers_with_obs_fold(Config) when is_list(Config) ->
%%-------------------------------------------------------------------------
+headers_conflict_chunked_with_length(doc) ->
+ ["Test the code for handling headers with both Transfer-Encoding"
+ "and Content-Length which must receive error in default (not relaxed) mode"
+ "and must receive successful response in relaxed mode"];
+headers_conflict_chunked_with_length(Config) when is_list(Config) ->
+ Request = {url(group_name(Config), "/headers_conflict_chunked_with_length.html", Config), []},
+ {error, {could_not_parse_as_http, _}} = httpc:request(get, Request, [{relaxed, false}], []),
+ {ok,{{_,200,_},_,_}} = httpc:request(get, Request, [{relaxed, true}], []),
+ ok.
+
+%%-------------------------------------------------------------------------
+
invalid_headers(Config) ->
Request = {url(group_name(Config), "/dummy.html", Config), [{"cookie", undefined}]},
{error, _} = httpc:request(get, Request, [], []).
@@ -1043,8 +1098,6 @@ remote_socket_close_async(Config) when is_list(Config) ->
%%-------------------------------------------------------------------------
process_leak_on_keepalive(Config) ->
- {ok, ClosedSocket} = gen_tcp:listen(6666, [{active, false}]),
- ok = gen_tcp:close(ClosedSocket),
Request = {url(group_name(Config), "/dummy.html", Config), []},
HttpcHandlers0 = supervisor:which_children(httpc_handler_sup),
{ok, {{_, 200, _}, _, Body}} = httpc:request(get, Request, [], []),
@@ -1056,11 +1109,10 @@ process_leak_on_keepalive(Config) ->
ordsets:to_list(
ordsets:subtract(ordsets:from_list(HttpcHandlers1),
ordsets:from_list(HttpcHandlers0))),
- sys:replace_state(
- Pid, fun (State) ->
- Session = element(3, State),
- setelement(3, State, Session#session{socket=ClosedSocket})
- end),
+ State = sys:get_state(Pid),
+ #session{socket=Socket} = element(3, State),
+ gen_tcp:close(Socket),
+
{ok, {{_, 200, _}, _, Body}} = httpc:request(get, Request, [], []),
%% bad handler with the closed socket should get replaced by
%% the new one, so children count should stay the same
@@ -1179,6 +1231,22 @@ wait_for_whole_response(Config) when is_list(Config) ->
ReqSeqNumServer ! shutdown.
%%--------------------------------------------------------------------
+stream_fun_server_close() ->
+ [{doc, "Test that an error msg is received when using a receiver fun as stream target"}].
+stream_fun_server_close(Config) when is_list(Config) ->
+ Request = {url(group_name(Config), "/delay_close.html", Config), []},
+ Self = self(),
+ Fun = fun(X) -> Self ! X end,
+ {ok, RequestId} = httpc:request(get, Request, [], [{sync, false}, {receiver, Fun}]),
+ receive
+ {RequestId, {error, Reason}} ->
+ ct:pal("Close ~p", [Reason]),
+ ok
+ after 13000 ->
+ ct:fail(did_not_receive_close)
+ end.
+
+%%--------------------------------------------------------------------
%% Internal Functions ------------------------------------------------
%%--------------------------------------------------------------------
stream(ReceiverPid, Receiver, Config) ->
@@ -1291,13 +1359,13 @@ group_name(Config) ->
server_start(sim_http, _) ->
Inet = inet_version(),
ok = httpc:set_options([{ipfamily, Inet}]),
- {_Pid, Port} = dummy_server(Inet),
+ {_Pid, Port} = http_test_lib:dummy_server(ip_comm, Inet, [{content_cb, ?MODULE}]),
Port;
server_start(sim_https, SslConfig) ->
Inet = inet_version(),
ok = httpc:set_options([{ipfamily, Inet}]),
- {_Pid, Port} = dummy_server(ssl, Inet, SslConfig),
+ {_Pid, Port} = http_test_lib:dummy_server(ssl, Inet, [{ssl, SslConfig}, {content_cb, ?MODULE}]),
Port;
server_start(_, HttpdConfig) ->
@@ -1401,13 +1469,7 @@ receive_replys([ID|IDs]) ->
ct:pal({recived_canceld_id, Other})
end.
-%% Perform a synchronous stop
-dummy_server_stop(Pid) ->
- Pid ! {stop, self()},
- receive
- {stopped, Pid} ->
- ok
- end.
+
inet_version() ->
inet. %% Just run inet for now
@@ -1561,7 +1623,6 @@ handle_request(Module, Function, Args, Socket) ->
end.
handle_http_msg({Method, RelUri, _, {_, Headers}, Body}, Socket) ->
-
ct:print("Request: ~p ~p", [Method, RelUri]),
NextRequest =
@@ -1685,6 +1746,15 @@ content_length(["content-length:" ++ Value | _]) ->
content_length([_Head | Tail]) ->
content_length(Tail).
+handle_uri("GET","/dummy.html?foo=bar",_,_,_,_) ->
+ "HTTP/1.0 200 OK\r\n\r\nTEST";
+
+handle_uri("GET","/space%20.html",_,_,_,_) ->
+ Body = "<HTML><BODY>foobar</BODY></HTML>",
+ "HTTP/1.1 200 OK\r\n" ++
+ "Content-Length:" ++ integer_to_list(length(Body)) ++ "\r\n\r\n" ++
+ Body;
+
handle_uri(_,"/just_close.html",_,_,_,_) ->
close;
handle_uri(_,"/no_content.html",_,_,_,_) ->
@@ -1750,6 +1820,23 @@ handle_uri(_,"/301.html",Port,_,Socket,_) ->
"Content-Length:" ++ integer_to_list(length(Body))
++ "\r\n\r\n" ++ Body;
+
+handle_uri("HEAD","/301_rel_uri.html",_,_,_,_) ->
+ NewUri = "/dummy.html",
+ "HTTP/1.1 301 Moved Permanently\r\n" ++
+ "Location:" ++ NewUri ++ "\r\n" ++
+ "Content-Length:0\r\n\r\n";
+
+handle_uri(_,"/301_rel_uri.html",_,_,_,_) ->
+ NewUri = "/dummy.html",
+ Body = "<HTML><BODY><a href=" ++ NewUri ++
+ ">New place</a></BODY></HTML>",
+ "HTTP/1.1 301 Moved Permanently\r\n" ++
+ "Location:" ++ NewUri ++ "\r\n" ++
+ "Content-Length:" ++ integer_to_list(length(Body))
+ ++ "\r\n\r\n" ++ Body;
+
+
handle_uri("HEAD","/302.html",Port,_,Socket,_) ->
NewUri = url_start(Socket) ++
integer_to_list(Port) ++ "/dummy.html",
@@ -1853,7 +1940,6 @@ handle_uri(_,"/dummy_headers.html",_,_,Socket,_) ->
%% user to evaluate. This is not a valid response
%% it only tests that the header handling code works.
Head = "HTTP/1.1 200 ok\r\n" ++
- "Content-Length:32\r\n" ++
"Pragma:1#no-cache\r\n" ++
"Via:1.0 fred, 1.1 nowhere.com (Apache/1.1)\r\n" ++
"Warning:1#pseudonym foobar\r\n" ++
@@ -1883,6 +1969,15 @@ handle_uri(_,"/obs_folded_headers.html",_,_,_,_) ->
" b\r\n\r\n"
"Hello";
+handle_uri(_,"/headers_conflict_chunked_with_length.html",_,_,Socket,_) ->
+ Head = "HTTP/1.1 200 ok\r\n"
+ "Content-Length:32\r\n"
+ "Transfer-Encoding:Chunked\r\n\r\n",
+ send(Socket, Head),
+ send(Socket, http_chunk:encode("<HTML><BODY>fo")),
+ send(Socket, http_chunk:encode("obar</BODY></HTML>")),
+ http_chunk:encode_last();
+
handle_uri(_,"/capital_transfer_encoding.html",_,_,Socket,_) ->
Head = "HTTP/1.1 200 ok\r\n" ++
"Transfer-Encoding:Chunked\r\n\r\n",
@@ -2030,6 +2125,9 @@ handle_uri(_,"/multipart_chunks.html",_,_,Socket,_) ->
send(Socket, Head),
send_multipart_chunks(Socket),
http_chunk:encode_last();
+handle_uri(_,"/delay_close.html",_,_,Socket,_) ->
+ ct:sleep(10000),
+ close(Socket);
handle_uri("HEAD",_,_,_,_,_) ->
"HTTP/1.1 200 ok\r\n" ++
"Content-Length:0\r\n\r\n";
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl
index 6c8728470b..9a85c51d24 100644
--- a/lib/inets/test/httpd_SUITE.erl
+++ b/lib/inets/test/httpd_SUITE.erl
@@ -129,7 +129,7 @@ groups() ->
{http_1_1, [],
[host, chunked, expect, cgi, cgi_chunked_encoding_test,
trace, range, if_modified_since, mod_esi_chunk_timeout,
- esi_put] ++ http_head() ++ http_get() ++ load()},
+ esi_put, esi_post] ++ http_head() ++ http_get() ++ load()},
{http_1_0, [], [host, cgi, trace] ++ http_head() ++ http_get() ++ load()},
{http_0_9, [], http_head() ++ http_get() ++ load()}
].
@@ -923,8 +923,11 @@ esi(Config) when is_list(Config) ->
{no_header, "cache-control"}]),
ok = http_status("GET /cgi-bin/erl/httpd_example:peer ",
Config, [{statuscode, 200},
- {header, "peer-cert-exist", peer(Config)}]).
-
+ {header, "peer-cert-exist", peer(Config)}]),
+ ok = http_status("GET /cgi-bin/erl/httpd_example:new_status_and_location ",
+ Config, [{statuscode, 201},
+ {header, "location"}]).
+
%%-------------------------------------------------------------------------
esi_put() ->
[{doc, "Test mod_esi PUT"}].
@@ -932,7 +935,20 @@ esi_put() ->
esi_put(Config) when is_list(Config) ->
ok = http_status("PUT /cgi-bin/erl/httpd_example/put/123342234123 ",
Config, [{statuscode, 200}]).
-
+%%-------------------------------------------------------------------------
+esi_post() ->
+ [{doc, "Test mod_esi POST"}].
+
+esi_post(Config) when is_list(Config) ->
+ Chunk = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ",
+ Data = lists:duplicate(10000, Chunk),
+ Length = lists:flatlength(Data),
+ ok = http_status("POST /cgi-bin/erl/httpd_example/post ",
+ {"Content-Length:" ++ integer_to_list(Length) ++ "\r\n",
+ Data},
+ [{http_version, "HTTP/1.1"} |Config],
+ [{statuscode, 200}]).
+
%%-------------------------------------------------------------------------
mod_esi_chunk_timeout(Config) when is_list(Config) ->
ok = httpd_1_1:mod_esi_chunk_timeout(proplists:get_value(type, Config),
diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl
index 931cd076cc..94d22ea76c 100644
--- a/lib/inets/test/httpd_basic_SUITE.erl
+++ b/lib/inets/test/httpd_basic_SUITE.erl
@@ -303,7 +303,10 @@ escaped_url_in_error_body(Config) when is_list(Config) ->
%% Ask for a non-existing page(1)
Path = "/<b>this_is_bold<b>",
HTMLEncodedPath = http_util:html_encode(Path),
- URL2 = URL1 ++ Path,
+ URL2 = uri_string:recompose(#{scheme => "http",
+ host => "localhost",
+ port => Port,
+ path => Path}),
{ok, {404, Body3}} = httpc:request(get, {URL2, []},
[{url_encode, true},
{version, "HTTP/1.0"}],
diff --git a/lib/inets/test/httpd_bench_SUITE.erl b/lib/inets/test/httpd_bench_SUITE.erl
new file mode 100644
index 0000000000..9d8cbf9ae2
--- /dev/null
+++ b/lib/inets/test/httpd_bench_SUITE.erl
@@ -0,0 +1,846 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2012. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+
+%%
+-module(httpd_bench_SUITE).
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/file.hrl").
+
+-define(remote_host, "NETMARKS_REMOTE_HOST").
+-define(LF, [10]).
+-define(CR, [13]).
+-define(CRLF, ?CR ++ ?LF).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+suite() ->
+ [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}].
+
+all() ->
+ [
+ {group, http_dummy},
+ {group, http_inets},
+ {group, http_nginx},
+ {group, https_inets},
+ {group, https_dummy},
+ {group, https_nginx},
+ {group, http_dummy_keep_alive},
+ {group, http_inets_keep_alive},
+ {group, http_nginx_keep_alive},
+ {group, https_inets_keep_alive},
+ {group, https_dummy_keep_alive},
+ {group, https_nginx_keep_alive}
+ ].
+
+groups() ->
+ [
+ {http_dummy, [], client_tests()},
+ {http_inets, [], client_tests()},
+ {http_nginx, [], client_tests()},
+ {https_dummy, [], client_tests()},
+ {https_inets, [], client_tests()},
+ {https_nginx, [], client_tests()},
+ {http_dummy_keep_alive, [], client_tests()},
+ {http_inets_keep_alive, [], client_tests()},
+ {http_nginx_keep_alive, [], client_tests()},
+ {https_dummy_keep_alive, [], client_tests()},
+ {https_inets_keep_alive, [], client_tests()},
+ {https_nginx_keep_alive, [], client_tests()}
+ ].
+
+
+client_tests() ->
+ [wget_small,
+ erl_dummy_small,
+ httpc_small,
+ wget_big,
+ erl_dummy_big,
+ httpc_big
+ ].
+
+init_per_suite(Config) ->
+ try
+ {Node, Host} = setup(Config, node()),
+ init_ssl(Config),
+ [{iter, 10}, {server_node, Node}, {server_host, Host} | Config]
+ catch _:_ ->
+ {skipped, "Benchmark machines only"}
+ end.
+
+end_per_suite(_Config) ->
+ [application:stop(App) || App <- [asn1, crypto, public_key, ssl, inets]].
+
+init_per_group(Group, Config) when Group == http_dummy_keep_alive;
+ Group == https_dummy_keep_alive;
+ Group == http_inets_keep_alive;
+ Group == https_inets_keep_alive;
+ Group == http_nginx_keep_alive;
+ Group == https_nginx_keep_alive ->
+ Version = http_version(Group),
+ start_web_server(Group,
+ [{keep_alive, true},
+ {reuse_sessions, false},
+ {http_version, Version},
+ {http_opts,[{version, Version}]},
+ {http_headers, [{"connection", "keep-alive"}]},
+ {httpc_opts, [{keep_alive_timeout, 1500},
+ {max_keep_alive_length, ?config(iter, Config)}]}
+ | Config]);
+init_per_group(Group, Config) when Group == http_dummy;
+ Group == https_dummy;
+ Group == http_inets;
+ Group == https_inets;
+ Group == http_nginx;
+ Group == https_nginx ->
+ Version = http_version(Group),
+ start_web_server(Group,
+ [{keep_alive, false},
+ {reuse_sessions, false},
+ {http_version, Version},
+ {http_headers, [{"connection", "close"}]},
+ {http_opts,[{version, Version}]},
+ {httpc_opts, [{keep_alive_timeout, 0}, {max_keep_alive_length, 0}]}
+ | Config]);
+
+
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(Group, Config) ->
+ stop_web_server(Group, Config).
+
+init_per_testcase(TestCase, Config) when TestCase == httpc_small;
+ TestCase == httpc_big
+ ->
+ Opts = ?config(httpc_opts, Config),
+ inets:start(httpc, [{profile, TestCase}, {socket_opts, [{nodelay, true}]}]),
+ httpc:set_options(Opts, TestCase),
+ [{profile, TestCase} | proplists:delete(profile, Config)];
+
+init_per_testcase(_, Config) ->
+ Config.
+end_per_testcase(TestCase, _Config) when TestCase == httpc_small;
+ TestCase == httpc_big ->
+ ok = inets:stop(httpc, TestCase);
+end_per_testcase(_TestCase, Config) ->
+ Config.
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+erl_dummy_small(Config) when is_list(Config) ->
+ {ok, Result} = run_test(httpd_lib_client, "1k_file", Config),
+ notify(Result, Config, "erl_1k_file").
+
+erl_dummy_big(Config) when is_list(Config) ->
+ {ok, Result} = run_test(httpd_lib_client, "1M_file", Config),
+ notify(Result, Config, "erl_1M_file").
+
+wget_small(Config) when is_list(Config) ->
+ {ok, Result} = run_test(wget_client, "1k_file", Config),
+ notify(Result, Config, "wget_1k_file").
+
+wget_big(Config) when is_list(Config) ->
+ {ok, Result} = run_test(wget_client, "1M_file", Config),
+ notify(Result, Config, "wget_1M_file").
+
+httpc_small(Config) when is_list(Config) ->
+ {ok, Result} = run_test(httpc_client, "1k_file", Config),
+ notify(Result, Config, "httpc_1k_file").
+
+httpc_big(Config) when is_list(Config) ->
+ {ok, Result} = run_test(httpc_client, "1M_file", Config),
+ notify(Result, Config, "httpc_1M_file").
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Report benchmark results ------------------------------------------------
+%%--------------------------------------------------------------------
+
+notify({TestPerSec, _MBps}, Config, Suffix) ->
+ Name = lists:concat([?config(protocol,Config), " ",
+ server_name(Config, [dummy_pid, httpd_pid, nginx_port]),
+ "", Suffix]),
+ ct:comment("~p tps", [TestPerSec]),
+ ct_event:notify(#event{name = benchmark_data,
+ data=[{value, TestPerSec},
+ {suite, ?MODULE},
+ {name, Name}]}),
+ ok.
+%%--------------------------------------------------------------------
+%% Setup erlang nodes ------------------------------------------------
+%%--------------------------------------------------------------------
+
+server_name(Config, [Server | Rest]) ->
+ case proplists:get_value(Server, Config) of
+ undefined ->
+ server_name(Config, Rest);
+ _ ->
+ server_name(Server)
+ end.
+
+server_name(httpd_pid) ->
+ "inets";
+server_name(nginx_port) ->
+ "nginx";
+server_name(dummy_pid) ->
+ "erlang".
+
+setup(_Config, nonode@nohost) ->
+ exit(dist_not_enabled);
+setup(_Config, _LocalNode) ->
+ Host = case os:getenv(?remote_host) of
+ false ->
+ {ok, This} = inet:gethostname(),
+ This;
+ RemHost ->
+ RemHost
+ end,
+ Node = list_to_atom("inets_perf_server@" ++ Host),
+ SlaveArgs = case init:get_argument(pa) of
+ {ok, PaPaths} ->
+ lists:append([" -pa " ++ P || [P] <- PaPaths]);
+ _ -> []
+ end,
+ Prog =
+ case os:find_executable("erl") of
+ false -> "erl";
+ P -> P
+ end,
+ case net_adm:ping(Node) of
+ pong -> ok;
+ pang ->
+ {ok, Node} = slave:start(Host, inets_perf_server, SlaveArgs, no_link, Prog)
+ end,
+ Path = code:get_path(),
+ true = rpc:call(Node, code, set_path, [Path]),
+ [ensure_started(Node, App) || App <- [asn1, crypto, public_key, ssl, inets]],
+ [ensure_started(node(), App) || App <- [asn1, crypto, public_key, ssl, inets]],
+ (Node =:= node()) andalso restrict_schedulers(client),
+ {Node, Host}.
+
+ensure_started(Node, App) ->
+ ok = rpc:call(Node, application, ensure_started, [App]).
+
+
+restrict_schedulers(Type) ->
+ %% We expect this to run on 8 core machine
+ Extra0 = 1,
+ Extra = if (Type =:= server) -> -Extra0; true -> Extra0 end,
+ Scheds = erlang:system_info(schedulers),
+ erlang:system_flag(schedulers_online, (Scheds div 2) + Extra).
+
+%%--------------------------------------------------------------------
+%% Setup TLS input files ------------------------------------------------
+%%--------------------------------------------------------------------
+
+init_ssl(Config) ->
+ DDir = ?config(data_dir, Config),
+ PDir = ?config(priv_dir, Config),
+ {ok, _} = make_certs:all(DDir,
+ PDir).
+cert_opts(Config) ->
+ ClientCaCertFile = filename:join([?config(priv_dir, Config),
+ "client", "cacerts.pem"]),
+ ClientCertFile = filename:join([?config(priv_dir, Config),
+ "client", "cert.pem"]),
+ ServerCaCertFile = filename:join([?config(priv_dir, Config),
+ "server", "cacerts.pem"]),
+ ServerCertFile = filename:join([?config(priv_dir, Config),
+ "server", "cert.pem"]),
+ ServerKeyFile = filename:join([?config(priv_dir, Config),
+ "server", "key.pem"]),
+ ClientKeyFile = filename:join([?config(priv_dir, Config),
+ "client", "key.pem"]),
+ [{server_verification_opts, [{reuseaddr, true},
+ {cacertfile, ServerCaCertFile},
+ {ciphers, ["ECDHE-RSA-AES256-GCM-SHA384"]},
+ {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
+ {client_verification_opts, [
+ %%{verify, verify_peer},
+ {cacertfile, ClientCaCertFile},
+ {certfile, ClientCertFile},
+ {keyfile, ClientKeyFile}]}].
+
+%%--------------------------------------------------------------------
+%% Run clients ------------------------------------------------
+%%--------------------------------------------------------------------
+
+run_test(Client, File, Config) ->
+ Parent = self(),
+ Pid = spawn(fun() ->
+ receive
+ go ->
+ Parent ! {self(),
+ do_runs(Client, [{file, File} | Config])}
+ end
+ end),
+ Pid ! go,
+ receive
+ {Pid,{{tps, Tps}, {mbps, MBps}}} ->
+ ct:pal("Tps: ~p Bps~p", [Tps, MBps]),
+ {ok, {Tps, MBps}}
+ end.
+
+do_runs(Client, Config) ->
+ N = ?config(iter, Config),
+ DataDir = ?config(data_dir, Config),
+ File = ?config(file, Config),
+ Name = filename:join(DataDir, File),
+ Args = ?MODULE:Client(Config),
+ ?MODULE:Client({init, Args}),
+ Run =
+ fun() ->
+ ok = ?MODULE:Client(Args, N)
+ end,
+ {ok, Info} = file:read_file_info(Name, []),
+ Length = Info#file_info.size,
+ {TimeInMicro, _} = timer:tc(Run),
+ ReqPerSecond = (1000000 * N) div TimeInMicro,
+ BytesPerSecond = (1000000 * N * Length) div TimeInMicro,
+ {{tps, ReqPerSecond}, {mbps, BytesPerSecond}}.
+
+
+httpc_client({init, [_, Profile, URL, Headers, HTTPOpts]}) ->
+ %% Make sure pipelining feature will kick in when appropriate.
+ {ok, {{_ ,200, "OK"}, _,_}} = httpc:request(get,{URL, Headers}, HTTPOpts,
+ [{body_format, binary},
+ {socket_opts, [{nodelay, true}]}], Profile),
+ ct:sleep(1000);
+httpc_client(Config) ->
+ File = ?config(file, Config),
+ Protocol = ?config(protocol, Config),
+ Profile = ?config(profile, Config),
+ URL = (?config(urlfun,Config))(File),
+ Headers = ?config(http_headers, Config),
+ HTTPOpts = ?config(http_opts, Config),
+ [Protocol, Profile, URL, Headers, HTTPOpts].
+httpc_client(_,0) ->
+ ok;
+httpc_client([Protocol, Profile, URL, Headers, HTTPOpts], N) ->
+ {ok, {{_ ,200,"OK"}, _,_}} = httpc:request(get,{URL, Headers}, HTTPOpts, [{body_format, binary},
+ {socket_opts, [{nodelay, true}]}], Profile),
+ httpc_client([Protocol, Profile, URL, Headers, HTTPOpts], N-1).
+
+httpd_lib_client({init, [_, Type, Version, Request, Host, Port, Opts]}) ->
+ ok = httpd_test_lib:verify_request(Type, Host,
+ Port,
+ Opts, node(),
+ Request,
+ [{statuscode, 200},
+ {version, Version}], infinity),
+ ct:sleep(1000);
+httpd_lib_client(Config) ->
+ File = ?config(file, Config),
+ KeepAlive = ?config(keep_alive, Config),
+ Host = ?config(server_host, Config),
+ Port = ?config(port, Config),
+ ReuseSession = ?config(reuse_sessions, Config),
+ {Type, Opts} =
+ case ?config(protocol, Config) of
+ "http" ->
+ {ip_comm, [{active, true}, {mode, binary},{nodelay, true}]};
+ "https" ->
+ SSLOpts = proplists:get_value(client_verification_opts, cert_opts(Config)),
+ {ssl, [{active, true}, {mode, binary}, {nodelay, true},
+ {reuse_sessions, ReuseSession} | SSLOpts]}
+
+ end,
+ Version = ?config(http_version, Config),
+ Request = case KeepAlive of
+ true ->
+ http_request("GET /" ++ File ++ " ", Version, Host, {"connection:keep-alive\r\n", ""});
+ false ->
+ http_request("GET /" ++ File ++ " ", Version, Host)
+ end,
+
+ Args = [KeepAlive, Type, Version, Request, Host, Port, Opts],
+ httpd_lib_client(Args, 1),
+ Args.
+
+httpd_lib_client(_, 0) ->
+ ok;
+httpd_lib_client([true, Type, Version, Request, Host, Port, Opts], N) ->
+ ok = httpd_test_lib:verify_request_N(Type, Host,
+ Port,
+ Opts, node(),
+ Request,
+ [{statuscode, 200},
+ {version, Version}], infinity, N);
+httpd_lib_client([false, Type, Version, Request, Host, Port, Opts] = List, N) ->
+ ok = httpd_test_lib:verify_request(Type, Host,
+ Port,
+ Opts, node(),
+ Request,
+ [{statuscode, 200},
+ {version, Version}], infinity),
+ httpd_lib_client(List, N-1).
+
+wget_client({init,_}) ->
+ ok;
+wget_client(Config) ->
+ File = ?config(file, Config),
+ URL = (?config(urlfun,Config))(File),
+ KeepAlive = ?config(keep_alive, Config),
+ PrivDir = ?config(priv_dir, Config),
+ Protocol = ?config(protocol, Config),
+ Iter = ?config(iter, Config),
+ FileName = filename:join(PrivDir, "wget_req"),
+ ProtocolOpts = case Protocol of
+ "http" ->
+ [];
+ "https" ->
+ proplists:get_value(client_verification_opts, cert_opts(Config))
+ end,
+ wget_req_file(FileName,URL,Iter),
+ [KeepAlive, FileName, URL, Protocol, ProtocolOpts, Iter].
+wget_client([KeepAlive, WgetFile, _URL, Protocol, ProtocolOpts, _], _) ->
+ process_flag(trap_exit, true),
+ Cmd = wget_N(KeepAlive, WgetFile, Protocol, ProtocolOpts),
+ %%ct:pal("Wget cmd: ~p", [Cmd]),
+ Port = open_port({spawn, Cmd}, [stderr_to_stdout]),
+ wait_for_wget(Port).
+
+
+%%--------------------------------------------------------------------
+%% Start/stop servers ------------------------------------------------
+%%--------------------------------------------------------------------
+start_web_server(Group, Config) when Group == http_dummy;
+ Group == http_dummy_keep_alive ->
+ start_dummy("http", Config);
+
+start_web_server(Group, Config) when Group == https_dummy;
+ Group == https_dummy_keep_alive ->
+ start_dummy("https", Config);
+
+start_web_server(Group, Config) when Group == http_inets;
+ Group == http_inets_keep_alive ->
+ start_inets("http", [], Config);
+
+start_web_server(Group, Config) when Group == https_inets;
+ Group == https_inets_keep_alive ->
+ Opts = proplists:get_value(server_verification_opts, cert_opts(Config)),
+ ReuseSessions = ?config(reuse_sessions, Config),
+ SSLConfHttpd = [{socket_type, {essl,
+ [{nodelay, true}, {reuse_sessions, ReuseSessions} | Opts]}}],
+ start_inets("https", SSLConfHttpd, Config);
+
+start_web_server(Group, Config) when Group == http_nginx;
+ Group == http_nginx_keep_alive ->
+ case os:find_executable("nginx") of
+ false ->
+ {skip, "nginx not found"};
+ _ ->
+ start_nginx("http", Config)
+ end;
+
+start_web_server(Group, Config) when Group == https_nginx;
+ Group == https_nginx_keep_alive ->
+ case os:find_executable("nginx") of
+ false ->
+ {skip, "nginx not found"};
+ _ ->
+ start_nginx("https", cert_opts(Config) ++ Config)
+ end.
+
+start_inets(Protocol, ConfHttpd, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ Node = ?config(server_node, Config),
+ Host = ?config(server_host, Config),
+ HTTPVersion = ?config(http_version, Config),
+ Conf = [httpd, [{port,0},
+ {http_version, HTTPVersion},
+ {ipfamily, inet},
+ {server_name, "inets_test"},
+ {server_root, PrivDir},
+ {document_root, DataDir},
+ {keep_alive, ?config(keep_alive, Config)},
+ {keep_alive_timeout, 360}
+ | ConfHttpd]],
+ {ok, Pid} = rpc:call(Node, inets, start, Conf),
+ Port = proplists:get_value(port, rpc:call(Node, httpd, info, [Pid])),
+ F = fun(File) ->
+ lists:concat([Protocol,"://",Host,":",Port,"/",File])
+ end,
+ [{httpd_pid,Pid},{urlfun,F},{protocol,Protocol},{port,Port} | Config].
+
+start_dummy("http"= Protocol, Config) ->
+ HTTPVersion = ?config(http_version, Config),
+ Node = ?config(server_node, Config),
+ %%DataDir= ?config(data_dir, Config),
+ Host = ?config(server_host, Config),
+ Conf = [
+ %%{big, filename:join(DataDir, "1M_file")},
+ %%{small, filename:join(DataDir, "1k_file")},
+ {big, {gen, crypto:rand_bytes(1000000)}},
+ {small, {gen, crypto:rand_bytes(1000)}},
+ {http_version, HTTPVersion},
+ {keep_alive, ?config(keep_alive, Config)}
+ ],
+ {Pid, Port} = rpc:call(Node, http_test_lib, dummy_server, [ip_comm, inet, [{content_cb, ?MODULE}, {conf, Conf}]]),
+ F = fun(File) ->
+ lists:concat([Protocol,"://",Host,":",Port,"/",File])
+ end,
+ [{dummy_pid,Pid},{urlfun,F},{protocol, Protocol},{port,Port} | Config];
+
+start_dummy("https" = Protocol, Config) ->
+ HTTPVersion = ?config(http_version, Config),
+ Node = ?config(server_node, Config),
+ %% DataDir= ?config(data_dir, Config),
+ Host = ?config(server_host, Config),
+ SSLOpts = proplists:get_value(server_verification_opts, cert_opts(Config)),
+ Opts = [{active, true}, {nodelay, true}, {reuseaddr, true} | SSLOpts],
+ Conf = [%%{big, filename:join(DataDir, "1M_file")},
+ %%{small, filename:join(DataDir, "1k_file")},
+ {big, {gen, crypto:rand_bytes(1000000)}},
+ {small, {gen, crypto:rand_bytes(1000)}},
+ {http_version, HTTPVersion},
+ {keep_alive, ?config(keep_alive, Config)}
+ ],
+ {Pid, Port} = rpc:call(Node, http_test_lib, dummy_server,
+ [ssl, inet, [{ssl, Opts}, {content_cb, ?MODULE}, {conf, Conf}]]),
+ F = fun(File) ->
+ lists:concat([Protocol,"://",Host,":",Port,"/",File])
+ end,
+ [{dummy_pid,Pid},{urlfun,F},{protocol,Protocol},{port,Port} | Config].
+
+start_nginx(Protocol, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ Host = ?config(server_host, Config),
+ Port = inet_port(node()),
+
+ ConfFile = filename:join(PrivDir, "nginx.conf"),
+ nginx_conf(ConfFile, [{port, Port}, {protocol, Protocol} | Config]),
+ Cmd = "nginx -c " ++ ConfFile,
+ NginxPort = open_port({spawn, Cmd}, [{cd, DataDir}, stderr_to_stdout]),
+
+ F = fun(File) ->
+ lists:concat([Protocol,"://",Host,":",Port,"/",File])
+ end,
+
+ wait_for_nginx_up(Host, Port),
+
+ [{port, Port},{nginx_port, NginxPort},{urlfun,F},{protocol, Protocol} | Config ].
+
+stop_nginx(Config)->
+ PrivDir = ?config(priv_dir, Config),
+ {ok, Bin} = file:read_file(filename:join(PrivDir, "nginx.pid")),
+ Pid = string:strip(binary_to_list(Bin), right, $\n),
+ Cmd = "kill " ++ Pid,
+ os:cmd(Cmd).
+
+stop_web_server(Group, Config) when Group == http_inets;
+ Group == http_inets_keep_alive;
+ Group == https_inets;
+ Group == https_inets_keep_alive ->
+ ServerNode = ?config(server_node, Config),
+ rpc:call(ServerNode, inets, stop, [httpd, ?config(httpd_pid, Config)]);
+stop_web_server(Group, Config) when Group == http_dummy;
+ Group == http_dummy_keep_alive;
+ Group == https_dummy;
+ Group == https_dummy_keep_alive ->
+ stop_dummy_server(Config);
+stop_web_server(Group, Config) when Group == http_nginx;
+ Group == http_nginx_keep_alive;
+ Group == https_nginx;
+ Group == https_nginx_keep_alive ->
+ stop_nginx(Config).
+
+stop_dummy_server(Config) ->
+ case ?config(dummy_pid, Config) of
+ Pid when is_pid(Pid) ->
+ exit(Pid, kill);
+ _ ->
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+%% Misc ------------------------------------------------
+%%--------------------------------------------------------------------
+http_request(Request, "HTTP/1.1" = Version, Host, {Headers, Body}) ->
+ Request ++ Version ++ "\r\nhost:" ++ Host ++ "\r\n" ++ Headers ++ "\r\n" ++ Body;
+http_request(Request, Version, _, {Headers, Body}) ->
+ Request ++ Version ++ "\r\n" ++ Headers ++ "\r\n" ++ Body.
+
+http_request(Request, "HTTP/1.1" = Version, Host) ->
+ Request ++ Version ++ "\r\nhost:" ++ Host ++ "\r\n\r\n";
+http_request(Request, Version, _) ->
+ Request ++ Version ++ "\r\n\r\n".
+
+http_version(_) ->
+ "HTTP/1.1".
+
+inet_port(Node) ->
+ {Port, Socket} = do_inet_port(Node),
+ rpc:call(Node, gen_tcp, close, [Socket]),
+ Port.
+
+do_inet_port(Node) ->
+ {ok, Socket} = rpc:call(Node, gen_tcp, listen, [0, [{reuseaddr, true}]]),
+ {ok, Port} = rpc:call(Node, inet, port, [Socket]),
+ {Port, Socket}.
+
+%%--------------------------------------------------------------------
+%% Dummy server callbacks ------------------------------------------------
+%%--------------------------------------------------------------------
+
+handle_request(CB, S, "/1M_file" ++ _, Opts) ->
+ Name = proplists:get_value(big, Opts),
+ KeepAlive = proplists:get_value(keep_alive, Opts),
+ do_handle_request(CB, S, Name, Opts, KeepAlive);
+handle_request(CB, S, "/1k_file" ++ _, Opts) ->
+ Name = proplists:get_value(small, Opts),
+ KeepAlive = proplists:get_value(keep_alive, Opts),
+ do_handle_request(CB, S, Name, Opts, KeepAlive).
+
+do_handle_request(CB, S, Name, Opts, KeepAlive) when is_list(Name) ->
+ Version = proplists:get_value(http_version, Opts),
+ {ok, Fdesc} = file:open(Name, [read, binary]),
+ {ok, Info} = file:read_file_info(Name, []),
+ Length = Info#file_info.size,
+ Response = response_status_line_and_headers(Version, "Content-Length:"
+ ++ integer_to_list(Length) ++ ?CRLF, keep_alive(KeepAlive)),
+ CB:send(S, Response),
+ send_file(CB, S, Fdesc);
+do_handle_request(CB, S, {gen, Data}, Opts, KeepAlive) ->
+ Version = proplists:get_value(http_version, Opts),
+ Length = size(Data),
+ Response = response_status_line_and_headers(Version, "Content-Length:"
+ ++ integer_to_list(Length) ++ ?CRLF, keep_alive(KeepAlive)),
+ CB:send(S, Response),
+ send_file(CB, S, {gen, Data}).
+
+send_file(CB, S, {gen, Data}) ->
+ CB:send(S, Data);
+ %% ChunkSize = 64*1024,
+ %% case size(Data) of
+ %% N when N > ChunkSize ->
+ %% <<Chunk:N/binary, Rest/binary>> = Data,
+ %% %%{Chunk, Rest} = lists:split(N, Data),
+ %% CB:send(S, Chunk),
+ %% send_file(CB, S, {gen, Rest});
+ %% _ ->
+ %% CB:send(S, Data)
+ %% end;
+
+send_file(CB, S, FileDesc) ->
+ case file:read(FileDesc, 64*1024) of
+ {ok, Chunk} ->
+ CB:send(S, Chunk),
+ send_file(CB, S, FileDesc);
+ eof ->
+ file:close(FileDesc),
+ ok
+ end.
+
+response_status_line_and_headers(Version, Headers, ConnectionHeader) ->
+ StatusLine = [Version, " ", "200 OK", ?CRLF],
+ [StatusLine, Headers, ConnectionHeader, ?CRLF].
+
+keep_alive(true)->
+ "Connection:keep-alive\r\n";
+keep_alive(false) ->
+ "Connection:close\r\n".
+
+handle_http_msg({_Method, RelUri, _, {_, _Headers}, _Body}, Socket, Conf) ->
+ handle_request(connect_cb(Socket), Socket, RelUri, Conf),
+ case proplists:get_value(keep_alive, Conf) of
+ true ->
+ <<>>;
+ false ->
+ stop
+ end.
+
+connect_cb({sslsocket, _, _}) ->
+ ssl;
+connect_cb(_) ->
+ gen_tcp.
+
+%%--------------------------------------------------------------------
+%% Setup wget ------------------------------------------------
+%%--------------------------------------------------------------------
+wget_req_file(FileName, Url, Iter) ->
+ {ok, File} = file:open(FileName, [write]),
+ write_urls(File, Url, Iter).
+
+write_urls(File, Url, 1) ->
+ file:write(File, Url),
+ file:close(File);
+write_urls(File, Url, N) ->
+ file:write(File, Url),
+ file:write(File, "\n"),
+ write_urls(File, Url, N-1).
+
+wait_for_wget(Port) ->
+ receive
+ {Port, {data, _Data}} when is_port(Port) ->
+ wait_for_wget(Port);
+ {Port, closed} ->
+ ok;
+ {'EXIT', Port, _Reason} ->
+ ok
+ end.
+
+wget_N(KeepAlive, WegetFile, "http", _ProtocolOpts) ->
+ "wget -i " ++ WegetFile ++ " " ++ wget_keep_alive(KeepAlive) ++
+ " --no-cache --timeout=120" ;
+wget_N(KeepAlive, WegetFile, "https", ProtocolOpts) ->
+
+ "wget -i " ++ WegetFile ++ " " ++ wget_keep_alive(KeepAlive)
+ ++ wget_cert(ProtocolOpts) ++ wget_key(ProtocolOpts)
+ ++ wget_cacert(ProtocolOpts) ++
+ " --no-cache --timeout=120".
+
+wget(KeepAlive, URL, "http", _ProtocolOpts) ->
+ "wget " ++ URL ++ " " ++ wget_keep_alive(KeepAlive) ++
+ " --no-cache --timeout=120" ;
+wget(KeepAlive, URL, "https", ProtocolOpts) ->
+
+ "wget " ++ URL ++ " " ++ wget_keep_alive(KeepAlive)
+ ++ wget_cert(ProtocolOpts) ++ wget_key(ProtocolOpts)
+ ++ wget_cacert(ProtocolOpts) ++
+ " --no-cache --timeout=120".
+
+wget_keep_alive(true)->
+ "";
+wget_keep_alive(false) ->
+ "--no-http-keep-alive ".
+
+wget_cacert(ProtocolOpts) ->
+ "--ca-certificate=" ++ proplists:get_value(cacertfile, ProtocolOpts) ++ " ".
+
+wget_cert(ProtocolOpts) ->
+ "--certificate=" ++ proplists:get_value(certfile, ProtocolOpts) ++ " ".
+
+wget_key(ProtocolOpts) ->
+ "--private-key=" ++ proplists:get_value(keyfile, ProtocolOpts) ++ " ".
+
+%%--------------------------------------------------------------------
+%% Setup nginx ------------------------------------------------
+%%--------------------------------------------------------------------
+nginx_conf(ConfFile, Config)->
+ Protocol = ?config(protocol, Config),
+ file:write_file(ConfFile,
+ [format_nginx_conf(nginx_global(Config)),
+ format_nginx_conf(nginx_events(Config)),
+ format_nginx_conf(nginx_http(Protocol, Config))]).
+
+format_nginx_conf(Directives) ->
+ lists:map(fun({Key, Value}) ->
+ io_lib:format("~s ~s;\n", [Key, Value]);
+ (Str) ->
+ Str
+ end, Directives).
+
+
+nginx_global(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ [{"pid", filename:join(PrivDir, "nginx.pid")},
+ {"error_log", filename:join(PrivDir, "nginx.pid")},
+ {"worker_processes", "1"}].
+
+nginx_events(_Config) ->
+ ["events {\n",
+ {"worker_connections", "1024"},
+ "\n}"
+ ].
+
+nginx_http("http", Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ Port = ?config(port, Config),
+ ["http {\n" |
+ nginx_defaults(PrivDir) ++
+ [" server {",
+ {root, DataDir},
+ {listen, integer_to_list(Port)},
+ " location / {\n try_files $uri $uri/ /index.html;\n}"
+ "}\n", "}\n"
+ ]
+ ];
+
+nginx_http("https", Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ Port = ?config(port, Config),
+ SSLOpts = ?config(server_verification_opts, Config),
+ Ciphers = proplists:get_value(ciphers, SSLOpts),
+ ReuseSession = ?config(reuse_sessions, Config),
+ ["http {" |
+ nginx_defaults(PrivDir) ++
+ [" server {",
+ {"root", DataDir},
+ {"listen", integer_to_list(Port) ++ " ssl"},
+ {"ssl_certificate", ?config(certfile, SSLOpts)},
+ {"ssl_certificate_key", ?config(keyfile, SSLOpts)},
+ {"ssl_protocols", "TLSv1 TLSv1.1 TLSv1.2"},
+ {"ssl_ciphers", Ciphers},
+ {"ssl_session_cache", nginx_reuse_session(ReuseSession)},
+ " location / {\n try_files $uri $uri/ /index.html;\n}"
+ "}\n", "}\n"
+ ]
+ ].
+
+nginx_defaults(PrivDir) ->
+ [
+ %% Set temp and cache file options that will otherwise default to
+ %% restricted locations accessible only to root.
+ {"client_body_temp_path", filename:join(PrivDir, "client_body")},
+ {"fastcgi_temp_path", filename:join(PrivDir, "fastcgi_temp")},
+ {"proxy_temp_path", filename:join(PrivDir, "proxy_temp")},
+ {"scgi_temp_path", filename:join(PrivDir, "scgi_temp")},
+ {"uwsgi_temp_path", filename:join(PrivDir, "uwsgi_temp_path")},
+ {"access_log", filename:join(PrivDir, "access.log")},
+ {"error_log", filename:join(PrivDir, "error.log")},
+ %% Standard options
+ {"sendfile", "on"},
+ {"tcp_nopush", "on"},
+ {"tcp_nodelay", "on"},
+ {"keepalive_timeout", "360"},
+ {"types_hash_max_size", "2048"},
+ {"include", "/etc/nginx/mime.types"},
+ {"default_type", "application/octet-stream"}
+ ].
+
+nginx_reuse_session(true) ->
+ "on";
+nginx_reuse_session(false) ->
+ "off".
+
+wait_for_nginx_up(Host, Port) ->
+ case gen_tcp:connect(Host, Port, []) of
+ {ok, Socket} ->
+ gen_tcp:close(Socket);
+ _ ->
+ ct:sleep(100),
+ wait_for_nginx_up(Host, Port)
+ end.
+
diff --git a/lib/inets/test/httpd_bench_SUITE_data/1M_file b/lib/inets/test/httpd_bench_SUITE_data/1M_file
new file mode 100644
index 0000000000..557989144e
--- /dev/null
+++ b/lib/inets/test/httpd_bench_SUITE_data/1M_file
Binary files differ
diff --git a/lib/inets/test/httpd_bench_SUITE_data/1k_file b/lib/inets/test/httpd_bench_SUITE_data/1k_file
new file mode 100644
index 0000000000..cade172d80
--- /dev/null
+++ b/lib/inets/test/httpd_bench_SUITE_data/1k_file
Binary files differ
diff --git a/lib/inets/test/httpd_mod.erl b/lib/inets/test/httpd_mod.erl
index d9118aa1a4..2035b50248 100644
--- a/lib/inets/test/httpd_mod.erl
+++ b/lib/inets/test/httpd_mod.erl
@@ -779,9 +779,14 @@ esi(Type, Port, Host, Node) ->
[{statuscode, 200},
{no_header, "cache-control"},
{version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /cgi-bin/erl/httpd_example:new_status_and_location"
+ " HTTP/1.1\r\n\r\n",
+ [{statuscode, 201},
+ {header, "Location"},
+ {version, "HTTP/1.1"}]),
ok.
-
%%--------------------------------------------------------------------
get(Type, Port, Host, Node) ->
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
diff --git a/lib/inets/test/httpd_test_lib.erl b/lib/inets/test/httpd_test_lib.erl
index 1cecd2642c..b6525037b2 100644
--- a/lib/inets/test/httpd_test_lib.erl
+++ b/lib/inets/test/httpd_test_lib.erl
@@ -23,7 +23,8 @@
-include("inets_test_lib.hrl").
%% Poll functions
--export([verify_request/6, verify_request/7, verify_request/8, is_expect/1]).
+-export([verify_request/6, verify_request/7, verify_request/8, is_expect/1,
+ verify_request_N/9]).
-record(state, {request, % string()
socket, % socket()
@@ -109,9 +110,9 @@ verify_request(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options, Ti
{error, Reason};
NewState ->
ValidateResult =
- validate(RequestStr, NewState, Options, Node, Port),
+ validate(RequestStr, NewState, Options, Node, Port),
inets_test_lib:close(SocketType, Socket),
- ValidateResult
+ ValidateResult
end;
ConnectError ->
@@ -126,6 +127,46 @@ verify_request(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options, Ti
{args, [SocketType, Host, Port, TranspOpts]}]})
end.
+verify_request_N(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options, TimeOut, N) ->
+ State = #state{},
+ try inets_test_lib:connect_bin(SocketType, Host, Port, TranspOpts) of
+ {ok, Socket} ->
+ request_N(SocketType, Socket, RequestStr, Options, TimeOut, Node, Port, State, N);
+ ConnectError ->
+ ct:fail({connect_error, ConnectError,
+ [SocketType, Host, Port, TranspOpts]})
+ catch
+ T:E ->
+ ct:fail({connect_failure,
+ [{type, T},
+ {error, E},
+ {stacktrace, erlang:get_stacktrace()},
+ {args, [SocketType, Host, Port, TranspOpts]}]})
+ end.
+
+request_N(SocketType, Socket, RequestStr, Options, TimeOut, Node, Port, State, 0) ->
+ ok = inets_test_lib:send(SocketType, Socket, RequestStr),
+ case request(State#state{request = RequestStr,
+ socket = Socket}, TimeOut) of
+ {error, Reason} ->
+ {error, Reason};
+ NewState ->
+ ValidateResult =
+ validate(RequestStr, NewState, Options, Node, Port),
+ inets_test_lib:close(SocketType, Socket),
+ ValidateResult
+ end;
+request_N(SocketType, Socket, RequestStr, Options, TimeOut, Node, Port, State, N) ->
+ ok = inets_test_lib:send(SocketType, Socket, RequestStr),
+ case request(State#state{request = RequestStr,
+ socket = Socket}, TimeOut) of
+ {error, Reason} ->
+ {error, Reason};
+ _NewState ->
+ request_N(SocketType, Socket, RequestStr, Options, TimeOut, Node, Port,
+ #state{}, N-1)
+ end.
+
request(#state{mfa = {Module, Function, Args},
request = RequestStr, socket = Socket} = State, TimeOut) ->
@@ -160,13 +201,35 @@ request(#state{mfa = {Module, Function, Args},
{ssl_closed, Socket} ->
exit({test_failed, connection_closed});
{ssl_error, Socket, Reason} ->
- ct:fail({ssl_error, Reason})
+ ct:fail({ssl_error, Reason});
+ {Socket, {data, Data}} when is_port(Socket) ->
+ case Module:Function([list_to_binary(Data) | Args]) of
+ {ok, Parsed} ->
+ port_handle_http_msg(Parsed, State);
+ {_, whole_body, _} when HeadRequest =:= "HEAD" ->
+ State#state{body = <<>>};
+ NewMFA ->
+ request(State#state{mfa = NewMFA}, TimeOut)
+ end;
+ {Socket, closed} when Function =:= whole_body ->
+ State#state{body = hd(Args)};
+ {Socket, closed} ->
+ exit({test_failed, connection_closed})
after TimeOut ->
ct:pal("~p ~w[~w]request -> timeout"
- "~n", [self(), ?MODULE, ?LINE]),
+ "~p~n", [self(), ?MODULE, ?LINE, Args]),
ct:fail(connection_timed_out)
end.
+
+port_handle_http_msg({Version, StatusCode, ReasonPharse, Headers, Body}, State) ->
+ State#state{status_line = {Version,
+ StatusCode,
+ ReasonPharse},
+ headers = Headers,
+ body = Body}.
+
+
handle_http_msg({Version, StatusCode, ReasonPharse, Headers, Body},
State = #state{request = RequestStr}) ->
case is_expect(RequestStr) of
diff --git a/lib/inets/test/inets.spec b/lib/inets/test/inets.spec
index ed102f8219..6cb3d6526c 100644
--- a/lib/inets/test/inets.spec
+++ b/lib/inets/test/inets.spec
@@ -1 +1,3 @@
-{suites,"../inets_test",all}.
+{suites,"../inets_test", all}.
+{skip_suites, "../inets_test", [httpd_bench_SUITE],
+ "Benchmarks run separately"}.
diff --git a/lib/inets/test/inets_SUITE.erl b/lib/inets/test/inets_SUITE.erl
index 38b8229389..1abd96a228 100644
--- a/lib/inets/test/inets_SUITE.erl
+++ b/lib/inets/test/inets_SUITE.erl
@@ -213,7 +213,6 @@ start_httpd(Config) when is_list(Config) ->
true = lists:member(Pid0, Pids0),
[_|_] = inets:services_info(),
inets:stop(httpd, Pid0),
- ct:sleep(500),
Pids1 = [ServicePid || {_, ServicePid} <- inets:services()],
false = lists:member(Pid0, Pids1),
{ok, Pid0b} =
@@ -222,7 +221,6 @@ start_httpd(Config) when is_list(Config) ->
true = lists:member(Pid0b, Pids0b),
[_|_] = inets:services_info(),
inets:stop(httpd, Pid0b),
- ct:sleep(500),
Pids1 = [ServicePid || {_, ServicePid} <- inets:services()],
false = lists:member(Pid0b, Pids1),
{ok, Pid1} =
diff --git a/lib/inets/test/inets_bench.spec b/lib/inets/test/inets_bench.spec
new file mode 100644
index 0000000000..19136e691b
--- /dev/null
+++ b/lib/inets/test/inets_bench.spec
@@ -0,0 +1 @@
+{suites,"../inets_test",[httpd_bench_SUITE]}.
diff --git a/lib/inets/test/inets_test_lib.erl b/lib/inets/test/inets_test_lib.erl
index f1185f7574..2529cc5f9b 100644
--- a/lib/inets/test/inets_test_lib.erl
+++ b/lib/inets/test/inets_test_lib.erl
@@ -463,8 +463,9 @@ connect_bin(essl, Host, Port, Opts0) ->
connect(ssl, Host, Port, Opts);
connect_bin(ip_comm, Host, Port, Opts0) ->
Opts = [binary, {packet, 0} | Opts0],
- connect(ip_comm, Host, Port, Opts).
-
+ connect(ip_comm, Host, Port, Opts);
+connect_bin(Type, Host, Port, Opts) ->
+ connect(Type, Host, Port, Opts).
connect_byte(SockType, Host, Port) ->
connect_byte(SockType, Host, Port, []).
@@ -477,27 +478,40 @@ connect_byte(essl, Host, Port, Opts0) ->
connect(ssl, Host, Port, Opts);
connect_byte(ip_comm, Host, Port, Opts0) ->
Opts = [{packet,0} | Opts0],
- connect(ip_comm, Host, Port, Opts).
+ connect(ip_comm, Host, Port, Opts);
+connect_byte(Type, Host, Port, Opts) ->
+ connect(Type, Host, Port, Opts).
connect(ip_comm, Host, Port, Opts) ->
gen_tcp:connect(Host, Port, Opts);
connect(ssl, Host, Port, Opts) ->
- ssl:connect(Host, Port, Opts).
+ ssl:connect(Host, Port, Opts);
+connect(openssl_port, Host, Port, Opts) ->
+ CaCertFile = proplists:get_value(cacertfile, Opts),
+ Cmd = "openssl s_client -quiet -port " ++ integer_to_list(Port) ++ " -host " ++ Host
+ ++ " -CAfile " ++ CaCertFile,
+ ct:log("openssl cmd: ~p~n", [Cmd]),
+ OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
+ read_junk(OpensslPort),
+ {ok, OpensslPort}.
send(ssl, Socket, Data) ->
ssl:send(Socket, Data);
send(essl, Socket, Data) ->
ssl:send(Socket, Data);
send(ip_comm,Socket,Data) ->
- gen_tcp:send(Socket,Data).
-
-
+ gen_tcp:send(Socket,Data);
+send(openssl_port, Port, Data) ->
+ true = port_command(Port, Data),
+ ok.
close(ssl,Socket) ->
catch ssl:close(Socket);
close(essl,Socket) ->
catch ssl:close(Socket);
close(ip_comm,Socket) ->
- catch gen_tcp:close(Socket).
+ catch gen_tcp:close(Socket);
+close(openssl_port, Port) ->
+ exit(Port, normal).
hours(N) -> trunc(N * 1000 * 60 * 60).
@@ -572,3 +586,11 @@ do_inet_port(Node) ->
{ok, Socket} = rpc:call(Node, gen_tcp, listen, [0, [{reuseaddr, true}]]),
{ok, Port} = rpc:call(Node, inet, port, [Socket]),
{Port, Socket}.
+
+read_junk(OpensslPort) ->
+ receive
+ {OpensslPort, _} ->
+ read_junk(OpensslPort)
+ after 500 ->
+ ok
+ end.
diff --git a/lib/inets/test/make_certs.erl b/lib/inets/test/make_certs.erl
new file mode 100644
index 0000000000..7215a59823
--- /dev/null
+++ b/lib/inets/test/make_certs.erl
@@ -0,0 +1,530 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2015. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(make_certs).
+-compile([export_all]).
+
+%-export([all/1, all/2, rootCA/2, intermediateCA/3, endusers/3, enduser/3, revoke/3, gencrl/2, verify/3]).
+
+-record(config, {commonName,
+ organizationalUnitName = "Erlang OTP",
+ organizationName = "Ericsson AB",
+ localityName = "Stockholm",
+ countryName = "SE",
+ emailAddress = "[email protected]",
+ default_bits = 2048,
+ v2_crls = true,
+ ecc_certs = false,
+ issuing_distribution_point = false,
+ crl_port = 8000,
+ openssl_cmd = "openssl"}).
+
+
+default_config() ->
+ #config{}.
+
+make_config(Args) ->
+ make_config(Args, #config{}).
+
+make_config([], C) ->
+ C;
+make_config([{organizationalUnitName, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{organizationalUnitName = Name});
+make_config([{organizationName, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{organizationName = Name});
+make_config([{localityName, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{localityName = Name});
+make_config([{countryName, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{countryName = Name});
+make_config([{emailAddress, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{emailAddress = Name});
+make_config([{default_bits, Bits}|T], C) when is_integer(Bits) ->
+ make_config(T, C#config{default_bits = Bits});
+make_config([{v2_crls, Bool}|T], C) when is_boolean(Bool) ->
+ make_config(T, C#config{v2_crls = Bool});
+make_config([{crl_port, Port}|T], C) when is_integer(Port) ->
+ make_config(T, C#config{crl_port = Port});
+make_config([{ecc_certs, Bool}|T], C) when is_boolean(Bool) ->
+ make_config(T, C#config{ecc_certs = Bool});
+make_config([{issuing_distribution_point, Bool}|T], C) when is_boolean(Bool) ->
+ make_config(T, C#config{issuing_distribution_point = Bool});
+make_config([{openssl_cmd, Cmd}|T], C) when is_list(Cmd) ->
+ make_config(T, C#config{openssl_cmd = Cmd}).
+
+
+all([DataDir, PrivDir]) ->
+ all(DataDir, PrivDir).
+
+all(DataDir, PrivDir) ->
+ all(DataDir, PrivDir, #config{}).
+
+all(DataDir, PrivDir, C) when is_list(C) ->
+ all(DataDir, PrivDir, make_config(C));
+all(DataDir, PrivDir, C = #config{}) ->
+ ok = filelib:ensure_dir(filename:join(PrivDir, "erlangCA")),
+ create_rnd(DataDir, PrivDir), % For all requests
+ rootCA(PrivDir, "erlangCA", C),
+ intermediateCA(PrivDir, "otpCA", "erlangCA", C),
+ endusers(PrivDir, "otpCA", ["client", "server", "revoked", "a.server", "b.server"], C),
+ endusers(PrivDir, "erlangCA", ["localhost"], C),
+ %% Create keycert files
+ SDir = filename:join([PrivDir, "server"]),
+ SC = filename:join([SDir, "cert.pem"]),
+ SK = filename:join([SDir, "key.pem"]),
+ SKC = filename:join([SDir, "keycert.pem"]),
+ append_files([SK, SC], SKC),
+ CDir = filename:join([PrivDir, "client"]),
+ CC = filename:join([CDir, "cert.pem"]),
+ CK = filename:join([CDir, "key.pem"]),
+ CKC = filename:join([CDir, "keycert.pem"]),
+ append_files([CK, CC], CKC),
+ RDir = filename:join([PrivDir, "revoked"]),
+ RC = filename:join([RDir, "cert.pem"]),
+ RK = filename:join([RDir, "key.pem"]),
+ RKC = filename:join([RDir, "keycert.pem"]),
+ revoke(PrivDir, "otpCA", "revoked", C),
+ append_files([RK, RC], RKC),
+ remove_rnd(PrivDir),
+ {ok, C}.
+
+append_files(FileNames, ResultFileName) ->
+ {ok, ResultFile} = file:open(ResultFileName, [write]),
+ do_append_files(FileNames, ResultFile).
+
+do_append_files([], RF) ->
+ ok = file:close(RF);
+do_append_files([F|Fs], RF) ->
+ {ok, Data} = file:read_file(F),
+ ok = file:write(RF, Data),
+ do_append_files(Fs, RF).
+
+rootCA(Root, Name, C) ->
+ create_ca_dir(Root, Name, ca_cnf(C#config{commonName = Name})),
+ create_self_signed_cert(Root, Name, req_cnf(C#config{commonName = Name}), C),
+ file:copy(filename:join([Root, Name, "cert.pem"]), filename:join([Root, Name, "cacerts.pem"])),
+ gencrl(Root, Name, C).
+
+intermediateCA(Root, CA, ParentCA, C) ->
+ create_ca_dir(Root, CA, ca_cnf(C#config{commonName = CA})),
+ CARoot = filename:join([Root, CA]),
+ CnfFile = filename:join([CARoot, "req.cnf"]),
+ file:write_file(CnfFile, req_cnf(C#config{commonName = CA})),
+ KeyFile = filename:join([CARoot, "private", "key.pem"]),
+ ReqFile = filename:join([CARoot, "req.pem"]),
+ create_req(Root, CnfFile, KeyFile, ReqFile, C),
+ CertFile = filename:join([CARoot, "cert.pem"]),
+ sign_req(Root, ParentCA, "ca_cert", ReqFile, CertFile, C),
+ CACertsFile = filename:join(CARoot, "cacerts.pem"),
+ file:copy(filename:join([Root, ParentCA, "cacerts.pem"]), CACertsFile),
+ %% append this CA's cert to the cacerts file
+ {ok, Bin} = file:read_file(CertFile),
+ {ok, FD} = file:open(CACertsFile, [append]),
+ file:write(FD, ["\n", Bin]),
+ file:close(FD),
+ gencrl(Root, CA, C).
+
+endusers(Root, CA, Users, C) ->
+ [enduser(Root, CA, User, C) || User <- Users].
+
+enduser(Root, CA, User, C) ->
+ UsrRoot = filename:join([Root, User]),
+ file:make_dir(UsrRoot),
+ CnfFile = filename:join([UsrRoot, "req.cnf"]),
+ file:write_file(CnfFile, req_cnf(C#config{commonName = User})),
+ KeyFile = filename:join([UsrRoot, "key.pem"]),
+ ReqFile = filename:join([UsrRoot, "req.pem"]),
+ create_req(Root, CnfFile, KeyFile, ReqFile, C),
+ %create_req(Root, CnfFile, KeyFile, ReqFile),
+ CertFileAllUsage = filename:join([UsrRoot, "cert.pem"]),
+ sign_req(Root, CA, "user_cert", ReqFile, CertFileAllUsage, C),
+ CertFileDigitalSigOnly = filename:join([UsrRoot, "digital_signature_only_cert.pem"]),
+ sign_req(Root, CA, "user_cert_digital_signature_only", ReqFile, CertFileDigitalSigOnly, C),
+ CACertsFile = filename:join(UsrRoot, "cacerts.pem"),
+ file:copy(filename:join([Root, CA, "cacerts.pem"]), CACertsFile),
+ ok.
+
+revoke(Root, CA, User, C) ->
+ UsrCert = filename:join([Root, User, "cert.pem"]),
+ CACnfFile = filename:join([Root, CA, "ca.cnf"]),
+ Cmd = [C#config.openssl_cmd, " ca"
+ " -revoke ", UsrCert,
+ [" -crl_reason keyCompromise" || C#config.v2_crls ],
+ " -config ", CACnfFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env),
+ gencrl(Root, CA, C).
+
+gencrl(Root, CA, C) ->
+ CACnfFile = filename:join([Root, CA, "ca.cnf"]),
+ CACRLFile = filename:join([Root, CA, "crl.pem"]),
+ Cmd = [C#config.openssl_cmd, " ca"
+ " -gencrl ",
+ " -crlhours 24",
+ " -out ", CACRLFile,
+ " -config ", CACnfFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env).
+
+verify(Root, CA, User, C) ->
+ CAFile = filename:join([Root, User, "cacerts.pem"]),
+ CACRLFile = filename:join([Root, CA, "crl.pem"]),
+ CertFile = filename:join([Root, User, "cert.pem"]),
+ Cmd = [C#config.openssl_cmd, " verify"
+ " -CAfile ", CAFile,
+ " -CRLfile ", CACRLFile, %% this is undocumented, but seems to work
+ " -crl_check ",
+ CertFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ try cmd(Cmd, Env) catch
+ exit:{eval_cmd, _, _} ->
+ invalid
+ end.
+
+create_self_signed_cert(Root, CAName, Cnf, C = #config{ecc_certs = true}) ->
+ CARoot = filename:join([Root, CAName]),
+ CnfFile = filename:join([CARoot, "req.cnf"]),
+ file:write_file(CnfFile, Cnf),
+ KeyFile = filename:join([CARoot, "private", "key.pem"]),
+ CertFile = filename:join([CARoot, "cert.pem"]),
+ Cmd = [C#config.openssl_cmd, " ecparam"
+ " -out ", KeyFile,
+ " -name secp521r1 ",
+ %" -name sect283k1 ",
+ " -genkey "],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env),
+
+ Cmd2 = [C#config.openssl_cmd, " req"
+ " -new"
+ " -x509"
+ " -config ", CnfFile,
+ " -key ", KeyFile,
+ " -outform PEM ",
+ " -out ", CertFile],
+ cmd(Cmd2, Env);
+create_self_signed_cert(Root, CAName, Cnf, C) ->
+ CARoot = filename:join([Root, CAName]),
+ CnfFile = filename:join([CARoot, "req.cnf"]),
+ file:write_file(CnfFile, Cnf),
+ KeyFile = filename:join([CARoot, "private", "key.pem"]),
+ CertFile = filename:join([CARoot, "cert.pem"]),
+ Cmd = [C#config.openssl_cmd, " req"
+ " -new"
+ " -x509"
+ " -config ", CnfFile,
+ " -keyout ", KeyFile,
+ " -outform PEM",
+ " -out ", CertFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env).
+
+
+create_ca_dir(Root, CAName, Cnf) ->
+ CARoot = filename:join([Root, CAName]),
+ ok = filelib:ensure_dir(CARoot),
+ file:make_dir(CARoot),
+ create_dirs(CARoot, ["certs", "crl", "newcerts", "private"]),
+ create_rnd(Root, filename:join([CAName, "private"])),
+ create_files(CARoot, [{"serial", "01\n"},
+ {"crlnumber", "01"},
+ {"index.txt", ""},
+ {"ca.cnf", Cnf}]).
+
+create_req(Root, CnfFile, KeyFile, ReqFile, C = #config{ecc_certs = true}) ->
+ Cmd = [C#config.openssl_cmd, " ecparam"
+ " -out ", KeyFile,
+ " -name secp521r1 ",
+ %" -name sect283k1 ",
+ " -genkey "],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env),
+ Cmd2 = [C#config.openssl_cmd, " req"
+ " -new ",
+ " -key ", KeyFile,
+ " -outform PEM ",
+ " -out ", ReqFile,
+ " -config ", CnfFile],
+ cmd(Cmd2, Env);
+ %fix_key_file(KeyFile).
+create_req(Root, CnfFile, KeyFile, ReqFile, C) ->
+ Cmd = [C#config.openssl_cmd, " req"
+ " -new"
+ " -config ", CnfFile,
+ " -outform PEM ",
+ " -keyout ", KeyFile,
+ " -out ", ReqFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env).
+ %fix_key_file(KeyFile).
+
+
+sign_req(Root, CA, CertType, ReqFile, CertFile, C) ->
+ CACnfFile = filename:join([Root, CA, "ca.cnf"]),
+ Cmd = [C#config.openssl_cmd, " ca"
+ " -batch"
+ " -notext"
+ " -config ", CACnfFile,
+ " -extensions ", CertType,
+ " -in ", ReqFile,
+ " -out ", CertFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env).
+
+%%
+%% Misc
+%%
+
+create_dirs(Root, Dirs) ->
+ lists:foreach(fun(Dir) ->
+ file:make_dir(filename:join([Root, Dir])) end,
+ Dirs).
+
+create_files(Root, NameContents) ->
+ lists:foreach(
+ fun({Name, Contents}) ->
+ file:write_file(filename:join([Root, Name]), Contents) end,
+ NameContents).
+
+create_rnd(FromDir, ToDir) ->
+ From = filename:join([FromDir, "RAND"]),
+ To = filename:join([ToDir, "RAND"]),
+ file:copy(From, To).
+
+remove_rnd(Dir) ->
+ File = filename:join([Dir, "RAND"]),
+ file:delete(File).
+
+cmd(Cmd, Env) ->
+ FCmd = lists:flatten(Cmd),
+ Port = open_port({spawn, FCmd}, [stream, eof, exit_status, stderr_to_stdout,
+ {env, Env}]),
+ eval_cmd(Port, FCmd).
+
+eval_cmd(Port, Cmd) ->
+ receive
+ {Port, {data, _}} ->
+ eval_cmd(Port, Cmd);
+ {Port, eof} ->
+ ok
+ end,
+ receive
+ {Port, {exit_status, 0}} ->
+ ok;
+ {Port, {exit_status, Status}} ->
+ exit({eval_cmd, Cmd, Status})
+ after 0 ->
+ ok
+ end.
+
+%%
+%% Contents of configuration files
+%%
+
+req_cnf(C) ->
+ ["# Purpose: Configuration for requests (end users and CAs)."
+ "\n"
+ "ROOTDIR = $ENV::ROOTDIR\n"
+ "\n"
+
+ "[req]\n"
+ "input_password = secret\n"
+ "output_password = secret\n"
+ "default_bits = ", integer_to_list(C#config.default_bits), "\n"
+ "RANDFILE = $ROOTDIR/RAND\n"
+ "encrypt_key = no\n"
+ "default_md = md5\n"
+ "#string_mask = pkix\n"
+ "x509_extensions = ca_ext\n"
+ "prompt = no\n"
+ "distinguished_name= name\n"
+ "\n"
+
+ "[name]\n"
+ "commonName = ", C#config.commonName, "\n"
+ "organizationalUnitName = ", C#config.organizationalUnitName, "\n"
+ "organizationName = ", C#config.organizationName, "\n"
+ "localityName = ", C#config.localityName, "\n"
+ "countryName = ", C#config.countryName, "\n"
+ "emailAddress = ", C#config.emailAddress, "\n"
+ "\n"
+
+ "[ca_ext]\n"
+ "basicConstraints = critical, CA:true\n"
+ "keyUsage = cRLSign, keyCertSign\n"
+ "subjectKeyIdentifier = hash\n"
+ "subjectAltName = email:copy\n"].
+
+ca_cnf(C = #config{issuing_distribution_point = true}) ->
+ ["# Purpose: Configuration for CAs.\n"
+ "\n"
+ "ROOTDIR = $ENV::ROOTDIR\n"
+ "default_ca = ca\n"
+ "\n"
+
+ "[ca]\n"
+ "dir = $ROOTDIR/", C#config.commonName, "\n"
+ "certs = $dir/certs\n"
+ "crl_dir = $dir/crl\n"
+ "database = $dir/index.txt\n"
+ "new_certs_dir = $dir/newcerts\n"
+ "certificate = $dir/cert.pem\n"
+ "serial = $dir/serial\n"
+ "crl = $dir/crl.pem\n",
+ ["crlnumber = $dir/crlnumber\n" || C#config.v2_crls],
+ "private_key = $dir/private/key.pem\n"
+ "RANDFILE = $dir/private/RAND\n"
+ "\n"
+ "x509_extensions = user_cert\n",
+ ["crl_extensions = crl_ext\n" || C#config.v2_crls],
+ "unique_subject = no\n"
+ "default_days = 3600\n"
+ "default_md = md5\n"
+ "preserve = no\n"
+ "policy = policy_match\n"
+ "\n"
+
+ "[policy_match]\n"
+ "commonName = supplied\n"
+ "organizationalUnitName = optional\n"
+ "organizationName = match\n"
+ "countryName = match\n"
+ "localityName = match\n"
+ "emailAddress = supplied\n"
+ "\n"
+
+ "[crl_ext]\n"
+ "authorityKeyIdentifier=keyid:always,issuer:always\n",
+ ["issuingDistributionPoint=critical, @idpsec\n" || C#config.issuing_distribution_point],
+
+ "[idpsec]\n"
+ "fullname=URI:http://localhost:8000/",C#config.commonName,"/crl.pem\n"
+
+ "[user_cert]\n"
+ "basicConstraints = CA:false\n"
+ "keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n"
+ "subjectKeyIdentifier = hash\n"
+ "authorityKeyIdentifier = keyid,issuer:always\n"
+ "subjectAltName = email:copy\n"
+ "issuerAltName = issuer:copy\n"
+ "crlDistributionPoints=@crl_section\n"
+
+ "[crl_section]\n"
+ %% intentionally invalid
+ "URI.1=http://localhost/",C#config.commonName,"/crl.pem\n"
+ "URI.2=http://localhost:",integer_to_list(C#config.crl_port),"/",C#config.commonName,"/crl.pem\n"
+ "\n"
+
+ "[user_cert_digital_signature_only]\n"
+ "basicConstraints = CA:false\n"
+ "keyUsage = digitalSignature\n"
+ "subjectKeyIdentifier = hash\n"
+ "authorityKeyIdentifier = keyid,issuer:always\n"
+ "subjectAltName = email:copy\n"
+ "issuerAltName = issuer:copy\n"
+ "\n"
+
+ "[ca_cert]\n"
+ "basicConstraints = critical,CA:true\n"
+ "keyUsage = cRLSign, keyCertSign\n"
+ "subjectKeyIdentifier = hash\n"
+ "authorityKeyIdentifier = keyid:always,issuer:always\n"
+ "subjectAltName = email:copy\n"
+ "issuerAltName = issuer:copy\n"
+ "crlDistributionPoints=@crl_section\n"
+ ];
+
+ca_cnf(C = #config{issuing_distribution_point = false}) ->
+ ["# Purpose: Configuration for CAs.\n"
+ "\n"
+ "ROOTDIR = $ENV::ROOTDIR\n"
+ "default_ca = ca\n"
+ "\n"
+
+ "[ca]\n"
+ "dir = $ROOTDIR/", C#config.commonName, "\n"
+ "certs = $dir/certs\n"
+ "crl_dir = $dir/crl\n"
+ "database = $dir/index.txt\n"
+ "new_certs_dir = $dir/newcerts\n"
+ "certificate = $dir/cert.pem\n"
+ "serial = $dir/serial\n"
+ "crl = $dir/crl.pem\n",
+ ["crlnumber = $dir/crlnumber\n" || C#config.v2_crls],
+ "private_key = $dir/private/key.pem\n"
+ "RANDFILE = $dir/private/RAND\n"
+ "\n"
+ "x509_extensions = user_cert\n",
+ ["crl_extensions = crl_ext\n" || C#config.v2_crls],
+ "unique_subject = no\n"
+ "default_days = 3600\n"
+ "default_md = md5\n"
+ "preserve = no\n"
+ "policy = policy_match\n"
+ "\n"
+
+ "[policy_match]\n"
+ "commonName = supplied\n"
+ "organizationalUnitName = optional\n"
+ "organizationName = match\n"
+ "countryName = match\n"
+ "localityName = match\n"
+ "emailAddress = supplied\n"
+ "\n"
+
+ "[crl_ext]\n"
+ "authorityKeyIdentifier=keyid:always,issuer:always\n",
+ %["issuingDistributionPoint=critical, @idpsec\n" || C#config.issuing_distribution_point],
+
+ %"[idpsec]\n"
+ %"fullname=URI:http://localhost:8000/",C#config.commonName,"/crl.pem\n"
+
+ "[user_cert]\n"
+ "basicConstraints = CA:false\n"
+ "keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n"
+ "subjectKeyIdentifier = hash\n"
+ "authorityKeyIdentifier = keyid,issuer:always\n"
+ "subjectAltName = email:copy\n"
+ "issuerAltName = issuer:copy\n"
+ %"crlDistributionPoints=@crl_section\n"
+
+ %%"[crl_section]\n"
+ %% intentionally invalid
+ %%"URI.1=http://localhost/",C#config.commonName,"/crl.pem\n"
+ %%"URI.2=http://localhost:",integer_to_list(C#config.crl_port),"/",C#config.commonName,"/crl.pem\n"
+ %%"\n"
+
+ "[user_cert_digital_signature_only]\n"
+ "basicConstraints = CA:false\n"
+ "keyUsage = digitalSignature\n"
+ "subjectKeyIdentifier = hash\n"
+ "authorityKeyIdentifier = keyid,issuer:always\n"
+ "subjectAltName = email:copy\n"
+ "issuerAltName = issuer:copy\n"
+ "\n"
+
+ "[ca_cert]\n"
+ "basicConstraints = critical,CA:true\n"
+ "keyUsage = cRLSign, keyCertSign\n"
+ "subjectKeyIdentifier = hash\n"
+ "authorityKeyIdentifier = keyid:always,issuer:always\n"
+ "subjectAltName = email:copy\n"
+ "issuerAltName = issuer:copy\n"
+ %"crlDistributionPoints=@crl_section\n"
+ ].
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index 34b6902747..05cf4f6cc3 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 6.4.2
+INETS_VSN = 6.4.5
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"