Skip to content

Commit ec6def4

Browse files
dhoardfstab
authored andcommitted
Changes to HTTPServer and HTTPMetricHandler to resolve getting Content-Length=0 when Transfer-Encoding=chunked. Refactored test classes for easier/cleaner testing.
Signed-off-by: Doug Hoard <[email protected]>
1 parent fd931c1 commit ec6def4

File tree

4 files changed

+577
-236
lines changed

4 files changed

+577
-236
lines changed

simpleclient_httpserver/src/main/java/io/prometheus/client/exporter/HTTPServer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ public void handle(HttpExchange t) throws IOException {
116116
}
117117
} else {
118118
long contentLength = response.size();
119-
t.getResponseHeaders().set("Content-Length", String.valueOf(contentLength));
119+
if (contentLength > 0) {
120+
t.getResponseHeaders().set("Content-Length", String.valueOf(contentLength));
121+
}
120122
if (t.getRequestMethod().equals("HEAD")) {
121123
contentLength = -1;
122124
}
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
package io.prometheus.client.exporter;
2+
3+
import javax.net.ssl.HostnameVerifier;
4+
import javax.net.ssl.HttpsURLConnection;
5+
import javax.net.ssl.SSLContext;
6+
import javax.net.ssl.TrustManager;
7+
import javax.xml.bind.DatatypeConverter;
8+
import java.io.IOException;
9+
import java.io.UnsupportedEncodingException;
10+
import java.net.HttpURLConnection;
11+
import java.net.URL;
12+
import java.net.URLConnection;
13+
import java.security.GeneralSecurityException;
14+
import java.security.KeyManagementException;
15+
import java.security.NoSuchAlgorithmException;
16+
import java.util.HashMap;
17+
import java.util.LinkedList;
18+
import java.util.List;
19+
import java.util.Map;
20+
import java.util.Scanner;
21+
import java.util.Set;
22+
23+
/**
24+
* Class to perform HTTP testing
25+
*/
26+
public class HttpRequest {
27+
28+
enum METHOD { GET, HEAD }
29+
30+
private final Configuration configuration;
31+
32+
/**
33+
* Constructor
34+
*
35+
* @param configuration configuration
36+
*/
37+
private HttpRequest(Configuration configuration) {
38+
this.configuration = configuration;
39+
}
40+
41+
/**
42+
* Method to execute an HTTP request
43+
*
44+
* @return HttpResponse
45+
* @throws IOException
46+
*/
47+
public HttpResponse execute() throws IOException {
48+
if (configuration.url.toLowerCase().startsWith("https://") && (configuration.trustManagers != null)) {
49+
try {
50+
SSLContext sslContext = SSLContext.getInstance("SSL");
51+
sslContext.init(null, configuration.trustManagers, new java.security.SecureRandom());
52+
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
53+
} catch (GeneralSecurityException e) {
54+
throw new IOException(e);
55+
}
56+
57+
if (configuration.hostnameVerifier != null) {
58+
HttpsURLConnection.setDefaultHostnameVerifier(configuration.hostnameVerifier);
59+
}
60+
}
61+
62+
URLConnection urlConnection = new URL(configuration.url).openConnection();
63+
((HttpURLConnection) urlConnection).setRequestMethod(configuration.method.toString());
64+
65+
Set<Map.Entry<String, List<String>>> entries = configuration.headers.entrySet();
66+
for (Map.Entry<String, List<String>> entry : entries) {
67+
for (String value : entry.getValue()) {
68+
urlConnection.addRequestProperty(entry.getKey(), value);
69+
}
70+
}
71+
72+
urlConnection.setUseCaches(false);
73+
urlConnection.setDoInput(true);
74+
urlConnection.setDoOutput(true);
75+
urlConnection.connect();
76+
77+
Scanner scanner = new Scanner(urlConnection.getInputStream(), "UTF-8").useDelimiter("\\A");
78+
79+
return new HttpResponse(
80+
((HttpURLConnection) urlConnection).getResponseCode(),
81+
urlConnection.getHeaderFields(),
82+
urlConnection.getContentLength(), scanner.hasNext() ? scanner.next() : "");
83+
}
84+
85+
/**
86+
* Class to build an HttpRequest
87+
*/
88+
static class Builder {
89+
90+
private final Configuration configuration;
91+
92+
/**
93+
* Constructor
94+
*/
95+
public Builder() {
96+
configuration = new Configuration();
97+
}
98+
99+
/**
100+
* Method to set the HTTP request method
101+
*
102+
* @param method
103+
* @return Builder
104+
*/
105+
public Builder withMethod(METHOD method) {
106+
configuration.method = method;
107+
return this;
108+
}
109+
110+
/**
111+
* Method to set the HTTP request URL
112+
*
113+
* @param url
114+
* @return Builder
115+
*/
116+
public Builder withURL(String url) {
117+
configuration.url = url;
118+
return this;
119+
}
120+
121+
/**
122+
* Method to add an HTTP request header
123+
*
124+
* @param name
125+
* @param value
126+
* @return Builder
127+
*/
128+
public Builder withHeader(String name, String value) {
129+
configuration.addHeader(name, value);
130+
return this;
131+
}
132+
133+
/**
134+
* Method to set the HTTP request "Authorization" header
135+
*
136+
* @param username
137+
* @param password
138+
* @return Builder
139+
*/
140+
public Builder withAuthorization(String username, String password) {
141+
configuration.setHeader("Authorization", encodeCredentials(username, password));
142+
return this;
143+
}
144+
145+
/**
146+
* Method to set the HTTP request trust managers when using an SSL URL
147+
*
148+
* @param trustManagers
149+
* @return Builder
150+
*/
151+
public Builder withTrustManagers(TrustManager[] trustManagers) {
152+
configuration.trustManagers = trustManagers;
153+
return this;
154+
}
155+
156+
/**
157+
* Method to set the HTTP request hostname verifier when using an SSL URL
158+
*
159+
* @param hostnameVerifier
160+
* @return Builder
161+
*/
162+
public Builder withHostnameVerifier(HostnameVerifier hostnameVerifier) {
163+
configuration.hostnameVerifier = hostnameVerifier;
164+
return this;
165+
}
166+
167+
/**
168+
* Method to build the HttpRequest
169+
*
170+
* @return HttpRequest
171+
*/
172+
public HttpRequest build() {
173+
return new HttpRequest(configuration);
174+
}
175+
}
176+
177+
/**
178+
* Class used for Builder configuration
179+
*/
180+
private static class Configuration {
181+
182+
public METHOD method;
183+
public String url;
184+
public Map<String, List<String>> headers;
185+
public TrustManager[] trustManagers;
186+
public HostnameVerifier hostnameVerifier;
187+
188+
/**
189+
* Constructor
190+
*/
191+
Configuration() {
192+
method = METHOD.GET;
193+
headers = new HashMap<String, List<String>>();
194+
}
195+
196+
/**
197+
* Method to add (append) an HTTP request header
198+
*
199+
* @param name
200+
* @param value
201+
* @return Configuration
202+
*/
203+
void addHeader(String name, String value) {
204+
name = name.toLowerCase();
205+
List<String> values = headers.get(name);
206+
if (values == null) {
207+
values = new LinkedList<String>();
208+
headers.put(name, values);
209+
}
210+
211+
values.add(value);
212+
}
213+
214+
/**
215+
* Method to set (overwrite) an HTTP request header, removing all previous header values
216+
*
217+
* @param name
218+
* @param value
219+
* @return Configuration
220+
*/
221+
void setHeader(String name, String value) {
222+
List<String> values = new LinkedList<String>();
223+
values.add(value);
224+
headers.put(name, values);
225+
}
226+
}
227+
228+
/**
229+
* Method to encode "Authorization" credentials
230+
*
231+
* @param username
232+
* @param password
233+
* @return String
234+
*/
235+
private final static String encodeCredentials(String username, String password) {
236+
// Per RFC4648 table 2. We support Java 6, and java.util.Base64 was only added in Java 8,
237+
try {
238+
byte[] credentialsBytes = (username + ":" + password).getBytes("UTF-8");
239+
return "Basic " + DatatypeConverter.printBase64Binary(credentialsBytes);
240+
} catch (UnsupportedEncodingException e) {
241+
throw new IllegalArgumentException(e);
242+
}
243+
}
244+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package io.prometheus.client.exporter;
2+
3+
import java.io.IOException;
4+
import java.util.HashMap;
5+
import java.util.List;
6+
import java.util.Map;
7+
import java.util.Set;
8+
9+
/**
10+
* Class to perform HTTP testing
11+
*/
12+
class HttpResponse {
13+
14+
private int responseCode;
15+
private Map<String, List<String>> headers;
16+
private long contentLength = -1;
17+
private String body;
18+
19+
/**
20+
* Constructor
21+
*
22+
* @param responseCode
23+
* @param headers
24+
* @param contentLength
25+
* @param body
26+
*/
27+
HttpResponse(int responseCode, Map<String, List<String>> headers, long contentLength, String body) throws IOException {
28+
this.responseCode = responseCode;
29+
this.body = body;
30+
this.contentLength = contentLength;
31+
this.headers = new HashMap<String, List<String>>();
32+
33+
Set<Map.Entry<String, List<String>>> headerSet = headers.entrySet();
34+
for (String header : headers.keySet()) {
35+
if (header != null) {
36+
List<String> values = headers.get(header);
37+
this.headers.put(header.toLowerCase(), values);
38+
}
39+
}
40+
41+
if (getHeader("content-length") != null && getHeader("transfer-encoding") != null) {
42+
throw new IOException("Invalid HTTP response, should only contain Connect-Length or Transfer-Encoding");
43+
}
44+
}
45+
46+
/**
47+
* Method to get the HTTP response code
48+
*
49+
* @return int
50+
*/
51+
public int getResponseCode() {
52+
return this.responseCode;
53+
}
54+
55+
/**
56+
* Method to get a list of HTTP response headers values
57+
*
58+
* @param name
59+
* @return List<String>
60+
*/
61+
public List<String> getHeaderList(String name) {
62+
return headers.get(name.toLowerCase());
63+
}
64+
65+
/**
66+
* Method to get the first HTTP response header value
67+
*
68+
* @param name
69+
* @return String
70+
*/
71+
public String getHeader(String name) {
72+
String value = null;
73+
74+
List<String> valueList = getHeaderList(name);
75+
if (valueList != null && (valueList.size() >= 0)) {
76+
value = valueList.get(0);
77+
}
78+
79+
return value;
80+
}
81+
82+
/**
83+
* Method to get the first HTTP response header value as a Long.
84+
* Returns null of the header doesn't exist
85+
*
86+
* @param name
87+
* @return Long
88+
*/
89+
public Long getHeaderAsLong(String name) {
90+
String value = getHeader(name);
91+
if (value != null) {
92+
try {
93+
return Long.valueOf(value);
94+
} catch (Exception e) {
95+
}
96+
}
97+
98+
return null;
99+
}
100+
101+
/**
102+
* Method to get the HTTP response body
103+
*
104+
* @return String
105+
*/
106+
public String getBody() {
107+
return body;
108+
}
109+
}

0 commit comments

Comments
 (0)