1
1
/*
2
- * Copyright 2013, 2014 Deutsche Nationalbibliothek
2
+ * Copyright 2013, 2022 Deutsche Nationalbibliothek et al
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 the "License";
5
5
* you may not use this file except in compliance with the License.
24
24
import org .metafacture .framework .annotations .Out ;
25
25
import org .metafacture .framework .helpers .DefaultObjectPipe ;
26
26
27
+ import java .io .ByteArrayInputStream ;
27
28
import java .io .IOException ;
29
+ import java .io .InputStream ;
28
30
import java .io .InputStreamReader ;
29
31
import java .io .Reader ;
32
+ import java .io .SequenceInputStream ;
33
+ import java .net .HttpURLConnection ;
30
34
import java .net .URL ;
31
- import java .net .URLConnection ;
32
35
import java .util .Arrays ;
33
36
import java .util .HashMap ;
34
37
import java .util .Map ;
35
38
import java .util .regex .Pattern ;
36
39
37
40
/**
38
- * Opens a {@link URLConnection } and passes a reader to the receiver.
41
+ * Opens an {@link HttpURLConnection } and passes a reader to the receiver.
39
42
*
40
43
* @author Christoph Böhme
41
44
* @author Jan Schnasse
45
+ * @author Jens Wille
42
46
*/
43
- @ Description ("Opens an HTTP resource. Supports the setting of `Accept` and `Accept-Charset` as HTTP header fields , as well as generic headers (separated by `\\ n`)." )
47
+ @ Description ("Opens an HTTP resource. Supports setting HTTP header fields `Accept`, `Accept-Charset` and `Content-Type` , as well as generic headers (separated by `\\ n`). Defaults: request `method` = `GET`, request `url` = `@-` (input data), request `body` = `@-` (input data) if request method supports body and input data not already used, `Accept` header = `*/*`, `Accept-Charset` header (`encoding`) = `UTF-8`, `errorPrefix` = `ERROR: ` ." )
44
48
@ In (String .class )
45
49
@ Out (Reader .class )
46
50
@ FluxCommand ("open-http" )
47
51
public final class HttpOpener extends DefaultObjectPipe <String , ObjectReceiver <Reader >> {
48
52
49
- private static final Pattern HEADER_FIELD_SEPARATOR = Pattern .compile ("\n " );
50
- private static final Pattern HEADER_VALUE_SEPARATOR = Pattern .compile (":" );
53
+ public static final String ACCEPT_DEFAULT = "*/*" ;
54
+ public static final String ACCEPT_HEADER = "accept" ;
55
+ public static final String CONTENT_TYPE_HEADER = "content-type" ;
56
+ public static final String DEFAULT_PREFIX = "ERROR: " ;
57
+ public static final String ENCODING_DEFAULT = "UTF-8" ;
58
+ public static final String ENCODING_HEADER = "accept-charset" ;
59
+ public static final String INPUT_DESIGNATOR = "@-" ;
51
60
52
- private static final String ACCEPT_HEADER = "accept " ;
53
- private static final String ENCODING_HEADER = "accept-charset" ;
61
+ public static final String DEFAULT_METHOD_NAME = "GET " ;
62
+ public static final Method DEFAULT_METHOD = Method . valueOf ( DEFAULT_METHOD_NAME ) ;
54
63
55
- private static final String ACCEPT_DEFAULT = "*/*" ;
56
- private static final String ENCODING_DEFAULT = "UTF-8" ;
64
+ public static final String HEADER_FIELD_SEPARATOR = "\n " ;
65
+ public static final String HEADER_VALUE_SEPARATOR = ":" ;
66
+
67
+ private static final Pattern HEADER_FIELD_SEPARATOR_PATTERN = Pattern .compile (HEADER_FIELD_SEPARATOR );
68
+ private static final Pattern HEADER_VALUE_SEPARATOR_PATTERN = Pattern .compile (HEADER_VALUE_SEPARATOR );
57
69
58
70
private final Map <String , String > headers = new HashMap <>();
59
71
72
+ private Method method ;
73
+ private String body ;
74
+ private String errorPrefix ;
75
+ private String url ;
76
+ private boolean inputUsed ;
77
+
78
+ public enum Method {
79
+
80
+ DELETE (false , true ),
81
+ GET (false , true ),
82
+ HEAD (false , false ),
83
+ OPTIONS (false , true ),
84
+ POST (true , true ),
85
+ PUT (true , true ),
86
+ TRACE (false , true );
87
+
88
+ private final boolean requestHasBody ;
89
+ private final boolean responseHasBody ;
90
+
91
+ Method (final boolean requestHasBody , final boolean responseHasBody ) {
92
+ this .requestHasBody = requestHasBody ;
93
+ this .responseHasBody = responseHasBody ;
94
+ }
95
+
96
+ /**
97
+ * Checks whether the request method accepts a request body.
98
+ *
99
+ * @return true if the request method accepts a request body
100
+ */
101
+ public boolean getRequestHasBody () {
102
+ return requestHasBody ;
103
+ }
104
+
105
+ /**
106
+ * Checks whether the request method returns a response body.
107
+ *
108
+ * @return true if the request method returns a response body
109
+ */
110
+ public boolean getResponseHasBody () {
111
+ return responseHasBody ;
112
+ }
113
+
114
+ }
115
+
60
116
/**
61
117
* Creates an instance of {@link HttpOpener}.
62
118
*/
63
119
public HttpOpener () {
64
120
setAccept (ACCEPT_DEFAULT );
65
121
setEncoding (ENCODING_DEFAULT );
122
+ setErrorPrefix (DEFAULT_PREFIX );
123
+ setMethod (DEFAULT_METHOD );
124
+ setUrl (INPUT_DESIGNATOR );
66
125
}
67
126
68
127
/**
69
- * Sets the HTTP accept header value. This is a mime-type such as text/plain
70
- * or text/html. The default value of the accept is */* which means
71
- * any mime-type.
128
+ * Sets the HTTP {@value ACCEPT_HEADER} header value. This is a MIME type
129
+ * such as {@code text/plain} or {@code application/json}. The default
130
+ * value for the accept header is {@value ACCEPT_DEFAULT} which means
131
+ * any MIME type.
72
132
*
73
- * @param accept mime- type to use for the HTTP accept header
133
+ * @param accept MIME type to use for the HTTP accept header
74
134
*/
75
135
public void setAccept (final String accept ) {
76
136
setHeader (ACCEPT_HEADER , accept );
77
137
}
78
138
79
139
/**
80
- * Sets the preferred encoding of the HTTP response. This value is in the
81
- * accept-charset header. Additonally, the encoding is used for reading the
82
- * HTTP resonse if it does not specify an encoding. The default value for
83
- * the encoding is UTF-8.
140
+ * Sets the HTTP request body. The default value for the request body is
141
+ * {@value INPUT_DESIGNATOR} <i>if the {@link #setMethod(Method) request
142
+ * method} accepts a request body</i>, which means it will use the {@link
143
+ * #process(String) input data} data as request body <i>if the input has
144
+ * not already been used</i>; otherwise, no request body will be set by
145
+ * default.
146
+ *
147
+ * <p>If a request body has been set, but the request method does not
148
+ * accept a body, the method <i>may</i> be changed to {@code POST}.
149
+ *
150
+ * @param body the request body
151
+ */
152
+ public void setBody (final String body ) {
153
+ this .body = body ;
154
+ }
155
+
156
+ /**
157
+ * Sets the HTTP {@value CONTENT_TYPE_HEADER} header value. This is a
158
+ * MIME type such as {@code text/plain} or {@code application/json}.
159
+ *
160
+ * @param contentType MIME type to use for the HTTP content-type header
161
+ */
162
+ public void setContentType (final String contentType ) {
163
+ setHeader (CONTENT_TYPE_HEADER , contentType );
164
+ }
165
+
166
+ /**
167
+ * Sets the HTTP {@value ENCODING_HEADER} header value. This is the
168
+ * preferred encoding for the HTTP response. Additionally, the encoding
169
+ * is used for reading the HTTP response if it does not specify a content
170
+ * encoding. The default for the encoding is {@value ENCODING_DEFAULT}.
84
171
*
85
172
* @param encoding name of the encoding used for the accept-charset HTTP
86
173
* header
@@ -90,14 +177,28 @@ public void setEncoding(final String encoding) {
90
177
}
91
178
92
179
/**
93
- * Sets a request property, or multiple request properties separated by
94
- * {@code \n}.
180
+ * Sets the error prefix. The default error prefix is
181
+ * {@value DEFAULT_PREFIX}.
182
+ *
183
+ * @param errorPrefix the error prefix
184
+ */
185
+ public void setErrorPrefix (final String errorPrefix ) {
186
+ this .errorPrefix = errorPrefix ;
187
+ }
188
+
189
+ /**
190
+ * Sets a request property (header), or multiple request properties
191
+ * separated by {@value HEADER_FIELD_SEPARATOR}. Header name and value
192
+ * are separated by {@value HEADER_VALUE_SEPARATOR}. The header name is
193
+ * case-insensitive.
95
194
*
96
195
* @param header request property line
196
+ *
197
+ * @see #setHeader(String, String)
97
198
*/
98
199
public void setHeader (final String header ) {
99
- Arrays .stream (HEADER_FIELD_SEPARATOR .split (header )).forEach (h -> {
100
- final String [] parts = HEADER_VALUE_SEPARATOR .split (h , 2 );
200
+ Arrays .stream (HEADER_FIELD_SEPARATOR_PATTERN .split (header )).forEach (h -> {
201
+ final String [] parts = HEADER_VALUE_SEPARATOR_PATTERN .split (h , 2 );
101
202
if (parts .length == 2 ) {
102
203
setHeader (parts [0 ], parts [1 ].trim ());
103
204
}
@@ -108,7 +209,7 @@ public void setHeader(final String header) {
108
209
}
109
210
110
211
/**
111
- * Sets a request property.
212
+ * Sets a request property (header). The header name is case-insensitive .
112
213
*
113
214
* @param key request property key
114
215
* @param value request property value
@@ -117,21 +218,99 @@ public void setHeader(final String key, final String value) {
117
218
headers .put (key .toLowerCase (), value );
118
219
}
119
220
221
+ /**
222
+ * Sets the HTTP request method. The default request method is
223
+ * {@value DEFAULT_METHOD_NAME}.
224
+ *
225
+ * @param method the request method
226
+ */
227
+ public void setMethod (final Method method ) {
228
+ this .method = method ;
229
+ }
230
+
231
+ /**
232
+ * Sets the HTTP request URL. The default value for the request URL is
233
+ * {@value INPUT_DESIGNATOR}, which means it will use the {@link
234
+ * #process(String) input data} as request URL.
235
+ *
236
+ * @param url the request URL
237
+ */
238
+ public void setUrl (final String url ) {
239
+ this .url = url ;
240
+ }
241
+
120
242
@ Override
121
- public void process (final String urlStr ) {
243
+ public void process (final String input ) {
122
244
try {
123
- final URL url = new URL (urlStr );
124
- final URLConnection con = url .openConnection ();
125
- headers .forEach (con ::addRequestProperty );
126
- String enc = con .getContentEncoding ();
127
- if (enc == null ) {
128
- enc = headers .get (ENCODING_HEADER );
245
+ final String requestUrl = getInput (input , url );
246
+ final String requestBody = getInput (input ,
247
+ body == null && method .getRequestHasBody () ? INPUT_DESIGNATOR : body );
248
+
249
+ final HttpURLConnection connection =
250
+ (HttpURLConnection ) new URL (requestUrl ).openConnection ();
251
+
252
+ connection .setRequestMethod (method .name ());
253
+ headers .forEach (connection ::addRequestProperty );
254
+
255
+ if (requestBody != null ) {
256
+ connection .setDoOutput (true );
257
+ connection .getOutputStream ().write (requestBody .getBytes ());
129
258
}
130
- getReceiver ().process (new InputStreamReader (con .getInputStream (), enc ));
259
+
260
+ final InputStream inputStream = getInputStream (connection );
261
+ final String contentEncoding = getEncoding (connection .getContentEncoding ());
262
+
263
+ getReceiver ().process (new InputStreamReader (inputStream , contentEncoding ));
131
264
}
132
265
catch (final IOException e ) {
133
266
throw new MetafactureException (e );
134
267
}
135
268
}
136
269
270
+ private String getInput (final String input , final String value ) {
271
+ final String result ;
272
+
273
+ if (!INPUT_DESIGNATOR .equals (value )) {
274
+ result = value ;
275
+ }
276
+ else if (inputUsed ) {
277
+ result = null ;
278
+ }
279
+ else {
280
+ inputUsed = true ;
281
+ result = input ;
282
+ }
283
+
284
+ return result ;
285
+ }
286
+
287
+ private InputStream getInputStream (final HttpURLConnection connection ) throws IOException {
288
+ try {
289
+ return connection .getInputStream ();
290
+ }
291
+ catch (final IOException e ) {
292
+ final InputStream errorStream = connection .getErrorStream ();
293
+ if (errorStream != null ) {
294
+ return getErrorStream (errorStream );
295
+ }
296
+ else {
297
+ throw e ;
298
+ }
299
+ }
300
+ }
301
+
302
+ private InputStream getErrorStream (final InputStream errorStream ) {
303
+ if (errorPrefix != null ) {
304
+ final InputStream errorPrefixStream = new ByteArrayInputStream (errorPrefix .getBytes ());
305
+ return new SequenceInputStream (errorPrefixStream , errorStream );
306
+ }
307
+ else {
308
+ return errorStream ;
309
+ }
310
+ }
311
+
312
+ private String getEncoding (final String contentEncoding ) {
313
+ return contentEncoding != null ? contentEncoding : headers .get (ENCODING_HEADER );
314
+ }
315
+
137
316
}
0 commit comments