From 45292e024894a6137c1691db218cd6ccd3916c0c Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Mon, 31 Oct 2011 16:05:34 +0100 Subject: [PATCH] Better implementation to get http header for http_client, and to get list of header entries by key,value --- .../http_client/src/http_client_response.e | 68 +++++++++++++- .../libcurl/libcurl_http_client_request.e | 57 +++++------- .../http_client/tests/test_http_client.e | 88 +++++++++++++++++++ 3 files changed, 172 insertions(+), 41 deletions(-) create mode 100644 library/client/http_client/tests/test_http_client.e diff --git a/library/client/http_client/src/http_client_response.e b/library/client/http_client/src/http_client_response.e index 5fc2cd7e..bafa8523 100644 --- a/library/client/http_client/src/http_client_response.e +++ b/library/client/http_client/src/http_client_response.e @@ -15,8 +15,9 @@ feature {NONE} -- Initialization make -- Initialize `Current'. do + --| Default values status := 200 - raw_headers := "" + create {STRING_8} raw_header.make_empty end feature -- Status @@ -35,37 +36,96 @@ feature {HTTP_CLIENT_REQUEST} -- Status setting feature -- Access status: INTEGER assign set_status + -- Status code of the response. - raw_headers: READABLE_STRING_8 + raw_header: READABLE_STRING_8 + -- Raw http header of the response. - headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8] + headers: LIST [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]] + -- Computed table of http headers of the response. local tb: like internal_headers + pos, l_start, l_end, n, c: INTEGER + h: like raw_header + k: STRING_8 do tb := internal_headers if tb = Void then create tb.make (3) + h := raw_header + from + pos := 1 + n := h.count + until + pos = 0 or pos > n + loop + l_start := pos + --| Left justify + from until not h[l_start].is_space loop + l_start := l_start + 1 + end + pos := h.index_of ('%N', l_start) + if pos > 0 then + l_end := pos - 1 + elseif l_start < n then + l_end := n + 1 + else + -- Empty line + l_end := 0 + end + if l_end > 0 then + --| Right justify + from until not h[l_end].is_space loop + l_end := l_end - 1 + end + c := h.index_of (':', l_start) + if c > 0 then + k := h.substring (l_start, c - 1) + k.right_adjust + c := c + 1 + from until c <= n and not h[c].is_space loop + c := c + 1 + end + tb.force ([k, h.substring (c, l_end)]) + else + check header_has_colon: c > 0 end + end + end + pos := pos + 1 + end internal_headers := tb end Result := tb end body: detachable READABLE_STRING_8 assign set_body + -- Content of the response feature -- Change set_status (s: INTEGER) + -- Set response `status' code to `s' do status := s end + set_raw_header (h: READABLE_STRING_8) + -- Set http header `raw_header' to `h' + do + raw_header := h + --| Reset internal headers + internal_headers := Void + end + set_body (s: like body) + -- Set `body' message to `s' do body := s end feature {NONE} -- Implementation - internal_headers: detachable like headers + internal_headers: detachable ARRAYED_LIST [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]] + -- Internal cached value for the headers end diff --git a/library/client/http_client/src/spec/libcurl/libcurl_http_client_request.e b/library/client/http_client/src/spec/libcurl/libcurl_http_client_request.e index 98012ac7..b29bfeee 100644 --- a/library/client/http_client/src/spec/libcurl/libcurl_http_client_request.e +++ b/library/client/http_client/src/spec/libcurl/libcurl_http_client_request.e @@ -45,7 +45,6 @@ feature -- Execution curl: CURL_EXTERNALS curl_easy: CURL_EASY_EXTERNALS curl_handle: POINTER - l_response : HTTP_CLIENT_RESPONSE do curl := session.curl curl_easy := session.curl_easy @@ -166,7 +165,6 @@ feature -- Execution l_headers as curs loop p := curl.slist_append (p, curs.key + ": " + curs.item) --- curl_easy.setopt_slist (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpheader, p) end end @@ -197,10 +195,8 @@ feature -- Execution else Result.status := 0 end - l_response := populate_response ( l_curl_string.string) - Result.headers.copy (l_response.headers) - Result.body := l_response.body + set_header_and_body_to (l_curl_string.string, Result) else Result.set_error_occurred (True) end @@ -209,41 +205,28 @@ feature -- Execution end - populate_response ( curl_response: STRING) : HTTP_CLIENT_RESPONSE - -- parse curl_response - -- if the header exist, extract header fields - -- and body and return an HTTP_CLIENT_RESPONSE + set_header_and_body_to (a_source: READABLE_STRING_8; res: HTTP_CLIENT_RESPONSE) + -- Parse `a_source' response + -- and set `header' and `body' from HTTP_CLIENT_RESPONSE `res' local - l_response : LIST[detachable STRING] - l_b: BOOLEAN - pos : INTEGER + pos, l_start : INTEGER do - create Result.make - if curl_response.has_substring ("%R%N%R%N") then -- has header - l_response := curl_response.split ('%R') - from - l_response.start - until - l_response.after or l_b - loop - if attached {STRING} l_response.item as l_item then - if l_item.has (':') then - pos := l_item.index_of (':', 1) - Result.headers.put (l_item.substring (pos+1, l_item.count), l_item.substring (2, pos-1)) - elseif l_item.starts_with ("%N") and then l_item.count = 1 then - l_b := true - end - end - l_response.forth - if l_b then - if attached {STRING} l_response.item as l_item then - l_item.remove_head (1) - Result.body := l_item - end - end - end + l_start := a_source.substring_index ("%R%N", 1) + if l_start > 0 then + --| Skip first line which is the status line + --| ex: HTTP/1.1 200 OK%R%N + l_start := l_start + 2 + end + if l_start < a_source.count and then a_source[l_start] = '%R' and a_source[l_start + 1] = '%N' then + res.set_body (a_source) else - Result.body := curl_response + pos := a_source.substring_index ("%R%N%R%N", l_start) + if pos > 0 then + res.set_raw_header (a_source.substring (l_start, pos + 1)) --| Keep the last %R%N + res.set_body (a_source.substring (pos + 4, a_source.count)) + else + res.set_body (a_source) + end end end end diff --git a/library/client/http_client/tests/test_http_client.e b/library/client/http_client/tests/test_http_client.e new file mode 100644 index 00000000..629a3ac5 --- /dev/null +++ b/library/client/http_client/tests/test_http_client.e @@ -0,0 +1,88 @@ +note + description: "[ + Eiffel tests that can be executed by testing tool. + ]" + author: "EiffelStudio test wizard" + date: "$Date$" + revision: "$Revision$" + testing: "type/manual" + +class + TEST_HTTP_CLIENT + +inherit + EQA_TEST_SET + +feature -- Test routines + + test_http_client + -- New test routine + local + sess: LIBCURL_HTTP_CLIENT_SESSION + h: STRING_8 + do + create sess.make ("http://www.google.com") + if attached sess.get ("/search?q=eiffel", Void) as res then + create h.make_empty + if attached res.headers as hds then + across + hds as c + loop + h.append (c.item.key + ": " + c.item.value + "%R%N") + end + end + if attached res.body as l_body then + + end + assert ("same headers", h.same_string (res.raw_header)) + else + assert ("Not found", False) + end + end + + test_headers + local + res: HTTP_CLIENT_RESPONSE + h: STRING + do + create res.make + create h.make_empty + h.append ("normal: NORMAL%R%N") + h.append ("concat: ABC%R%N") + h.append ("concat: DEF%R%N") + h.append ("key1: KEY%R%N") + h.append (" key2 : KEY%R%N") + h.append (" %T key3 : KEY%R%N") + h.append ("value1:VALUE%R%N") + h.append ("value2: VALUE%R%N") + h.append ("value3: VALUE%R%N") + h.append ("value4: VALUE %R%N") + h.append (" %Tfoo : BAR%T %R%N") + res.set_raw_header (h) + create h.make_empty + if attached res.headers as hds then + across + hds as c + loop + h.append (c.item.key + ": " + c.item.value + "%N") + end + end + assert ("Expected headers map", h.same_string ("[ + normal: NORMAL + concat: ABC + concat: DEF + key1: KEY + key2: KEY + key3: KEY + value1: VALUE + value2: VALUE + value3: VALUE + value4: VALUE + foo: BAR + + ]")) + end + +end + +