diff --git a/org.springframework.web/ivy.xml b/org.springframework.web/ivy.xml index e7db3ce64c..a7f0eb6578 100644 --- a/org.springframework.web/ivy.xml +++ b/org.springframework.web/ivy.xml @@ -29,28 +29,44 @@ - + - + - - - + + + - - - - - - - - + + + + + + + + - + + diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/HttpHeaders.java b/org.springframework.web/src/main/java/org/springframework/web/http/HttpHeaders.java new file mode 100644 index 0000000000..f33828a6ab --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/HttpHeaders.java @@ -0,0 +1,294 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.core.CollectionFactory; +import org.springframework.util.Assert; +import org.springframework.util.MediaType; +import org.springframework.util.StringUtils; + +/** + * Represents HTTP request and response headers, mapping string header names to list of string values. + * + *

In addition to the normal methods defined by {@link Map}, this class offers the following convenience methods: + *

+ * + *

Inspired by {@link com.sun.net.httpserver.Headers}. + * + * @author Arjen Poutsma + * @since 3.0 + */ +public final class HttpHeaders implements Map> { + + private static String ACCEPT = "Accept"; + + private static String ALLOW = "Allow"; + + private static String CONTENT_LENGTH = "Content-Length"; + + private static String CONTENT_TYPE = "Content-Type"; + + private static String LOCATION = "Location"; + + private Map> headers = CollectionFactory.createLinkedCaseInsensitiveMapIfPossible(5); + + /** + * Returns the list of acceptable {@link MediaType media types}, as specified by the Accept header.

+ * Returns an empty list when the acceptable media types are unspecified. + * + * @return the acceptable media types + */ + public List getAccept() { + String value = getFirst(ACCEPT); + return value != null ? MediaType.parseMediaTypes(value) : Collections.emptyList(); + } + + /** + * Sets the list of acceptable {@link MediaType media types}, as specified by the Accept header. + * + * @param acceptableMediaTypes the acceptable media types + */ + public void setAccept(List acceptableMediaTypes) { + set(ACCEPT, MediaType.toString(acceptableMediaTypes)); + } + + /** + * Returns the set of allowed {@link HttpMethod HTTP methods}, as specified by the Allow header.

+ * Returns an empty set when the allowed methods are unspecified. + * + * @return the allowed methods + */ + public EnumSet getAllow() { + String value = getFirst(ALLOW); + if (value != null) { + List allowedMethod = new ArrayList(5); + String[] tokens = value.split(",\\s*"); + for (String token : tokens) { + allowedMethod.add(HttpMethod.valueOf(token)); + } + return EnumSet.copyOf(allowedMethod); + } + else { + return EnumSet.noneOf(HttpMethod.class); + } + } + + /** + * Sets the set of allowed {@link HttpMethod HTTP methods}, as specified by the Allow header. + * + * @param allowedMethods the allowed methods + */ + public void setAllow(EnumSet allowedMethods) { + set(ALLOW, StringUtils.collectionToCommaDelimitedString(allowedMethods)); + } + + /** + * Returns the length of the body in bytes, as specified by the Content-Length header.

Returns -1 + * when the content-length is unknown. + * + * @return the content length + */ + public long getContentLength() { + String value = getFirst(CONTENT_LENGTH); + return value != null ? Long.parseLong(value) : -1; + } + + /** + * Sets the length of the body in bytes, as specified by the Content-Length header. + * + * @param contentLength the content length + */ + public void setContentLength(long contentLength) { + set(CONTENT_LENGTH, Long.toString(contentLength)); + } + + /** + * Returns the {@linkplain MediaType media type} of the body, as specified by the Content-Type header. + *

Returns null when the content-type is unknown. + * + * @return the content type + */ + public MediaType getContentType() { + String value = getFirst(CONTENT_TYPE); + return value != null ? MediaType.parseMediaType(value) : null; + } + + /** + * Sets the {@linkplain MediaType media type} of the body, as specified by the Content-Type header. + * + * @param mediaType the media type + */ + public void setContentType(MediaType mediaType) { + Assert.isTrue(!mediaType.isWildcardType(), "'Content-Type' cannot contain wildcard type '*'"); + Assert.isTrue(!mediaType.isWildcardSubtype(), "'Content-Type' cannot contain wildcard subtype '*'"); + set(CONTENT_TYPE, mediaType.toString()); + } + + /** + * Returns the (new) location of a resource, as specified by the Location header.

Returns + * null when the location is unknown. + * + * @return the location + */ + public URI getLocation() { + String value = getFirst(LOCATION); + return value != null ? URI.create(value) : null; + } + + /** + * Sets the (new) location of a resource, as specified by the Location header. + * + * @param location the location + */ + public void setLocation(URI location) { + set(LOCATION, location.toASCIIString()); + } + + /* + * Single string methods + */ + + /** + * Returns the first header value for the given header name, if any. + * + * @param headerName the header name + * @return the first header value; or null + */ + public String getFirst(String headerName) { + List headerValues = headers.get(headerName); + return headerValues != null ? headerValues.get(0) : null; + } + + /** + * Adds the given, single header value under the given name. + * + * @param headerName the header name + * @param headerValue the header value + * @throws UnsupportedOperationException if adding headers is not supported + * @see #put(String, List) + * @see #set(String, String) + */ + public void add(String headerName, String headerValue) { + List headerValues = headers.get(headerName); + if (headerValues == null) { + headerValues = new LinkedList(); + headers.put(headerName, headerValues); + } + headerValues.add(headerValue); + } + + /** + * Sets the given, single header value under the given name. + * + * @param headerName the header name + * @param headerValue the header value + * @throws UnsupportedOperationException if adding headers is not supported + * @see #put(String, List) + * @see #add(String, String) + */ + public void set(String headerName, String headerValue) { + List headerValues = new LinkedList(); + headerValues.add(headerValue); + headers.put(headerName, headerValues); + } + + /* + * Map implementation + */ + + public int size() { + return headers.size(); + } + + public boolean isEmpty() { + return headers.isEmpty(); + } + + public boolean containsKey(Object key) { + return headers.containsKey(key); + } + + public boolean containsValue(Object value) { + return headers.containsValue(value); + } + + public List get(Object key) { + return headers.get(key); + } + + public List put(String key, List value) { + return headers.put(key, value); + } + + public List remove(Object key) { + return headers.remove(key); + } + + public void putAll(Map> m) { + headers.putAll(m); + } + + public void clear() { + headers.clear(); + } + + public Set keySet() { + return headers.keySet(); + } + + public Collection> values() { + return headers.values(); + } + + public Set>> entrySet() { + return headers.entrySet(); + } + + @Override + public int hashCode() { + return headers.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj != null && obj instanceof HttpHeaders) { + HttpHeaders other = (HttpHeaders) obj; + return this.headers.equals(other.headers); + } + return false; + } + + @Override + public String toString() { + return headers.toString(); + } +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/HttpInputMessage.java b/org.springframework.web/src/main/java/org/springframework/web/http/HttpInputMessage.java new file mode 100644 index 0000000000..ad1f1e767b --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/HttpInputMessage.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Represents a HTTP output message, consisting of {@linkplain #getHeaders() headers} and a readable {@linkplain + * #getBody() body}.

Typically implemented by a HTTP request on the server-side, or a response on the client-side. + * + * @author Arjen Poutsma + * @since 3.0 + */ +public interface HttpInputMessage extends HttpMessage { + + /** + * Returns the body of the message as an input stream. + * + * @return the input stream body + * @throws IOException in case of I/O Errors + */ + InputStream getBody() throws IOException; + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/HttpMessage.java b/org.springframework.web/src/main/java/org/springframework/web/http/HttpMessage.java new file mode 100644 index 0000000000..305eef2da4 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/HttpMessage.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http; + +/** + * Represents the base interface for HTTP request and response messages. Consists of {@link HttpHeaders}, retrievable + * via {@link #getHeaders()}. + * + * @author Arjen Poutsma + * @since 3.0 + */ +public interface HttpMessage { + + /** + * Returns the headers of this message. + * + * @return the headers + */ + HttpHeaders getHeaders(); +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/HttpMethod.java b/org.springframework.web/src/main/java/org/springframework/web/http/HttpMethod.java new file mode 100644 index 0000000000..0d3a95d5db --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/HttpMethod.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http; + +/** + * Java 5 enumeration of HTTP request methods. + * + * @author Arjen Poutsma + * @see org.springframework.web.bind.annotation.RequestMapping + * @since 3.0 + */ +public enum HttpMethod { + + GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/HttpOutputMessage.java b/org.springframework.web/src/main/java/org/springframework/web/http/HttpOutputMessage.java new file mode 100644 index 0000000000..64c2c9bb5e --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/HttpOutputMessage.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Represents a HTTP output message, consisting of {@linkplain #getHeaders() headers} and a writable {@linkplain + * #getBody() body}.

Typically implemented by a HTTP request on the client-side, or a response on the server-side. + * + * @author Arjen Poutsma + * @since 3.0 + */ +public interface HttpOutputMessage extends HttpMessage { + + /** + * Returns the body of the message as an output stream. + * + * @return the output stream body + * @throws IOException in case of I/O Errors + */ + OutputStream getBody() throws IOException; + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/HttpStatus.java b/org.springframework.web/src/main/java/org/springframework/web/http/HttpStatus.java new file mode 100644 index 0000000000..5bb28a0958 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/HttpStatus.java @@ -0,0 +1,171 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http; + +/** + * Java 5 enumeration of HTTP status codes.

The HTTP status code series can be retrieved via {@link #series()}. + * + * @author Arjen Poutsma + * @see HttpStatus.Series + */ +public enum HttpStatus { + + // 1xx Informational + + CONTINUE(100), + SWITCHING_PROTOCOLS(101), + + // 2xx Success + + OK(200), + CREATED(201), + ACCEPTED(202), + NON_AUTHORITATIVE_INFORMATION(203), + NO_CONTENT(204), + RESET_CONTENT(205), + PARTIAL_CONTENT(206), + + // 3xx Redirection + + MULTIPLE_CHOICES(300), + MOVED_PERMANENTLY(301), + MOVED_TEMPORARILY(302), + SEE_OTHER(303), + NOT_MODIFIED(304), + USE_PROXY(305), + TEMPORARY_REDIRECT(307), + + // --- 4xx Client Error --- + + BAD_REQUEST(400), + UNAUTHORIZED(401), + PAYMENT_REQUIRED(402), + FORBIDDEN(403), + NOT_FOUND(404), + METHOD_NOT_ALLOWED(405), + NOT_ACCEPTABLE(406), + PROXY_AUTHENTICATION_REQUIRED(407), + REQUEST_TIMEOUT(408), + CONFLICT(409), + GONE(410), + LENGTH_REQUIRED(411), + PRECONDITION_FAILED(412), + REQUEST_TOO_LONG(413), + REQUEST_URI_TOO_LONG(414), + UNSUPPORTED_MEDIA_TYPE(415), + REQUESTED_RANGE_NOT_SATISFIABLE(416), + EXPECTATION_FAILED(417), + + // --- 5xx Server Error --- + + INTERNAL_SERVER_ERROR(500), + NOT_IMPLEMENTED(501), + BAD_GATEWAY(502), + SERVICE_UNAVAILABLE(503), + GATEWAY_TIMEOUT(504), + HTTP_VERSION_NOT_SUPPORTED(505); + + /** + * Java 5 enumeration of HTTP status series.

Retrievable via {@link HttpStatus#series()}. + */ + public enum Series { + + INFORMATIONAL(1), + SUCCESSFUL(2), + REDIRECTION(3), + CLIENT_ERROR(4), + SERVER_ERROR(5); + + private final int value; + + private Series(int value) { + this.value = value; + } + + /** + * Returns the integer value of this status series. Ranges from 1 to 5. + * + * @return the integer value + */ + public int value() { + return value; + } + + private static Series valueOf(HttpStatus status) { + int seriesCode = status.value() / 100; + for (Series series : values()) { + if (series.value == seriesCode) { + return series; + } + } + throw new IllegalArgumentException("No matching constant for [" + status + "]"); + } + + } + + private final int value; + + private HttpStatus(int value) { + this.value = value; + } + + /** + * Returns the integer value of this status code. + * + * @return the integer value + */ + public int value() { + return value; + } + + /** + * Returns the HTTP status series of this status code. + * + * @return the series + * @see HttpStatus.Series + */ + public Series series() { + return Series.valueOf(this); + } + + /** + * Returns the enum constant of this type with the specified numeric value. + * + * @param statusCode the numeric value of the enum to be returned + * @return the enum constant with the specified numeric value + * @throws IllegalArgumentException if this enum has no constant for the specified numeric value + */ + public static HttpStatus valueOf(int statusCode) { + for (HttpStatus status : values()) { + if (status.value == statusCode) { + return status; + } + } + throw new IllegalArgumentException("No matching constant for [" + statusCode + "]"); + } + + /** + * Returns a string representation of this status code. + * + * @return a string representation + */ + @Override + public String toString() { + return Integer.toString(value); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/client/ClientHttpRequest.java b/org.springframework.web/src/main/java/org/springframework/web/http/client/ClientHttpRequest.java new file mode 100644 index 0000000000..ab41cc7f5f --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/client/ClientHttpRequest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http.client; + +import java.io.IOException; + +import org.springframework.web.http.HttpMethod; +import org.springframework.web.http.HttpOutputMessage; + +/** + * Represents a client-side HTTP request. Created via an implementation of the {@link ClientHttpRequestFactory}.

A + * HttpRequest can be {@linkplain #execute() executed}, getting a {@link ClientHttpResponse} which can be + * read from. + * + * @author Arjen Poutsma + * @see ClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod) + */ +public interface ClientHttpRequest extends HttpOutputMessage { + + /** + * Returns the HTTP method of the request. + * + * @return the HTTP method + */ + HttpMethod getMethod(); + + /** + * Executes this request, resulting in a {@link ClientHttpResponse} that can be read. + * + * @return the response result of the execution + * @throws IOException in case of I/O errors + */ + ClientHttpResponse execute() throws IOException; + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/client/ClientHttpRequestFactory.java b/org.springframework.web/src/main/java/org/springframework/web/http/client/ClientHttpRequestFactory.java new file mode 100644 index 0000000000..d44282345a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/client/ClientHttpRequestFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http.client; + +import java.io.IOException; +import java.net.URI; + +import org.springframework.web.http.HttpMethod; + +/** + * Factory for {@link ClientHttpRequest} objects. Requests are created by the {@link #createRequest(URI, HttpMethod)} + * method. + * + * @author Arjen Poutsma + * @since 3.0 + */ +public interface ClientHttpRequestFactory { + + /** + * Creates a new {@link ClientHttpRequest} for the specified URI and HTTP method. The returned request can be written + * to, and then executed by calling {@link ClientHttpRequest#execute()}. + * + * @param uri the URI to create a request for + * @param httpMethod the HTTP method to execute + * @return the created request + * @throws IOException in case of I/O errors + */ + ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException; + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/client/ClientHttpResponse.java b/org.springframework.web/src/main/java/org/springframework/web/http/client/ClientHttpResponse.java new file mode 100644 index 0000000000..9523b72efd --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/client/ClientHttpResponse.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http.client; + +import java.io.IOException; + +import org.springframework.web.http.HttpInputMessage; +import org.springframework.web.http.HttpStatus; + +/** + * Represents a client-side HTTP response. Obtained via an calling of the {@link ClientHttpRequest#execute()}.

A + * HttpResponse must be {@linkplain #close() closed}, typically in a finally block. + * + * @author Arjen Poutsma + * @since 3.0 + */ +public interface ClientHttpResponse extends HttpInputMessage { + + /** + * Returns the HTTP status code of the response. + * + * @return the HTTP status + * @throws IOException in case of I/O errors + */ + HttpStatus getStatusCode() throws IOException; + + /** + * Returns the HTTP status text of the response. + * + * @return the HTTP status text + * @throws IOException in case of I/O errors + */ + String getStatusText() throws IOException; + + /** + * Closes this response, freeing any resources created. + */ + void close(); + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/client/SimpleClientHttpRequest.java b/org.springframework.web/src/main/java/org/springframework/web/http/client/SimpleClientHttpRequest.java new file mode 100644 index 0000000000..b0c950587d --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/client/SimpleClientHttpRequest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http.client; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.util.List; +import java.util.Map; + +import org.springframework.web.http.HttpHeaders; +import org.springframework.web.http.HttpMethod; + +/** + * {@link ClientHttpRequest} implementation that uses standard J2SE facilities to execute requests. Created via the + * {@link SimpleClientHttpRequestFactory}. + * + * @author Arjen Poutsma + * @see SimpleClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod) + * @since 3.0 + */ +final class SimpleClientHttpRequest implements ClientHttpRequest { + + private final HttpURLConnection connection; + + private final HttpHeaders headers = new HttpHeaders(); + + private boolean headersWritten = false; + + SimpleClientHttpRequest(HttpURLConnection connection) { + this.connection = connection; + } + + public HttpMethod getMethod() { + return HttpMethod.valueOf(connection.getRequestMethod()); + } + + public HttpHeaders getHeaders() { + return headers; + } + + public OutputStream getBody() throws IOException { + writeHeaders(); + return connection.getOutputStream(); + } + + public ClientHttpResponse execute() throws IOException { + writeHeaders(); + connection.connect(); + return new SimpleClientHttpResponse(connection); + } + + private void writeHeaders() { + if (!headersWritten) { + for (Map.Entry> entry : headers.entrySet()) { + String headerName = entry.getKey(); + for (String headerValue : entry.getValue()) { + connection.addRequestProperty(headerName, headerValue); + } + } + headersWritten = true; + } + } +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/client/SimpleClientHttpRequestFactory.java b/org.springframework.web/src/main/java/org/springframework/web/http/client/SimpleClientHttpRequestFactory.java new file mode 100644 index 0000000000..7260f693d8 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/client/SimpleClientHttpRequestFactory.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http.client; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; + +import org.springframework.util.Assert; +import org.springframework.web.http.HttpMethod; + +/** + * {@link ClientHttpRequestFactory} implementation that uses standard J2SE facilities. + * + * @author Arjen Poutsma + * @see java.net.HttpURLConnection + * @see org.springframework.web.http.client.commons.CommonsClientHttpRequestFactory + * @since 3.0 + */ +public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory { + + public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { + URL url = uri.toURL(); + URLConnection urlConnection = url.openConnection(); + Assert.isInstanceOf(HttpURLConnection.class, urlConnection); + HttpURLConnection connection = (HttpURLConnection) urlConnection; + prepareConnection(connection, httpMethod.name()); + return new SimpleClientHttpRequest(connection); + } + + /** + * Template method for preparing the given {@link HttpURLConnection}. + * + *

Default implementation prepares the connection for input and output, and sets the HTTP method. + * + * @param connection the connection to prepare + * @param httpMethod the HTTP request method ({@code GET}, {@code POST}, etc.) + * @throws IOException in case of I/O errors + */ + protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { + connection.setDoInput(true); + connection.setDoOutput(true); + connection.setRequestMethod(httpMethod); + } + +} \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/client/SimpleClientHttpResponse.java b/org.springframework.web/src/main/java/org/springframework/web/http/client/SimpleClientHttpResponse.java new file mode 100644 index 0000000000..63ab58f1e6 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/client/SimpleClientHttpResponse.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http.client; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; + +import org.springframework.util.StringUtils; +import org.springframework.web.http.HttpHeaders; +import org.springframework.web.http.HttpStatus; + +/** + * {@link ClientHttpResponse} implementation that uses standard J2SE facilities. Obtained via the {@link + * SimpleClientHttpRequest#execute()}. + * + * @author Arjen Poutsma + * @since 3.0 + */ +final class SimpleClientHttpResponse implements ClientHttpResponse { + + private final HttpURLConnection connection; + + private HttpHeaders headers; + + SimpleClientHttpResponse(HttpURLConnection connection) { + this.connection = connection; + } + + public HttpStatus getStatusCode() throws IOException { + return HttpStatus.valueOf(connection.getResponseCode()); + } + + public String getStatusText() throws IOException { + return connection.getResponseMessage(); + } + + public HttpHeaders getHeaders() { + if (headers == null) { + headers = new HttpHeaders(); + // Header field 0 is the status line, so we start at 1 + int i = 1; + while (true) { + String name = connection.getHeaderFieldKey(i); + if (!StringUtils.hasLength(name)) { + break; + } + headers.add(name, connection.getHeaderField(i)); + i++; + } + } + return headers; + } + + public InputStream getBody() throws IOException { + if (connection.getErrorStream() == null) { + return connection.getInputStream(); + } + else { + return connection.getErrorStream(); + } + } + + public void close() { + connection.disconnect(); + } +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/client/commons/CommonsClientHttpRequest.java b/org.springframework.web/src/main/java/org/springframework/web/http/client/commons/CommonsClientHttpRequest.java new file mode 100644 index 0000000000..43e6a1d6d8 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/client/commons/CommonsClientHttpRequest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http.client.commons; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; +import org.apache.commons.httpclient.methods.EntityEnclosingMethod; +import org.apache.commons.httpclient.methods.RequestEntity; + +import org.springframework.util.Assert; +import org.springframework.web.http.HttpHeaders; +import org.springframework.web.http.HttpMethod; +import org.springframework.web.http.client.ClientHttpRequest; +import org.springframework.web.http.client.ClientHttpResponse; + +/** + * {@link org.springframework.web.http.client.ClientHttpRequest} implementation that uses Commons Http Client to execute + * requests. Created via the {@link CommonsClientHttpRequestFactory}. + * + * @author Arjen Poutsma + * @see CommonsClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod) + */ +final class CommonsClientHttpRequest implements ClientHttpRequest { + + private final HttpClient httpClient; + + private final HttpMethodBase httpMethod; + + private final HttpHeaders headers = new HttpHeaders(); + + private boolean headersWritten = false; + + private ByteArrayOutputStream bufferedOutput; + + CommonsClientHttpRequest(HttpClient httpClient, HttpMethodBase httpMethod) { + this.httpClient = httpClient; + this.httpMethod = httpMethod; + } + + public HttpHeaders getHeaders() { + return headers; + } + + public OutputStream getBody() throws IOException { + writeHeaders(); + Assert.isInstanceOf(EntityEnclosingMethod.class, httpMethod); + this.bufferedOutput = new ByteArrayOutputStream(); + return bufferedOutput; + } + + public HttpMethod getMethod() { + return HttpMethod.valueOf(httpMethod.getName()); + } + + public ClientHttpResponse execute() throws IOException { + writeHeaders(); + if (httpMethod instanceof EntityEnclosingMethod) { + EntityEnclosingMethod entityEnclosingMethod = (EntityEnclosingMethod) httpMethod; + RequestEntity requestEntity = new ByteArrayRequestEntity(bufferedOutput.toByteArray()); + entityEnclosingMethod.setRequestEntity(requestEntity); + } + httpClient.executeMethod(httpMethod); + return new CommonsClientHttpResponse(httpMethod); + } + + private void writeHeaders() { + if (!headersWritten) { + for (Map.Entry> entry : headers.entrySet()) { + String headerName = entry.getKey(); + for (String headerValue : entry.getValue()) { + httpMethod.addRequestHeader(headerName, headerValue); + } + } + headersWritten = true; + } + } + +} \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/client/commons/CommonsClientHttpRequestFactory.java b/org.springframework.web/src/main/java/org/springframework/web/http/client/commons/CommonsClientHttpRequestFactory.java new file mode 100644 index 0000000000..cbe4e6cfc1 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/client/commons/CommonsClientHttpRequestFactory.java @@ -0,0 +1,148 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http.client.commons; + +import java.io.IOException; +import java.net.URI; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpConnectionManager; +import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.methods.DeleteMethod; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.HeadMethod; +import org.apache.commons.httpclient.methods.OptionsMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.methods.TraceMethod; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.util.Assert; +import org.springframework.web.http.HttpMethod; +import org.springframework.web.http.client.ClientHttpRequest; +import org.springframework.web.http.client.ClientHttpRequestFactory; + +/** + * {@link org.springframework.web.http.client.ClientHttpRequestFactory} implementation that uses Jakarta Commons HttpClient to create requests.

Allows to + * use a pre-configured {@link HttpClient} instance, potentially with authentication, HTTP connection pooling, etc. + * + * @author Arjen Poutsma + * @see org.springframework.web.http.client.SimpleClientHttpRequestFactory + */ +public class CommonsClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean { + + private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000); + + private HttpClient httpClient; + + /** + * Create a new instance of the CommonsHttpRequestFactory with a default {@link HttpClient} that uses a + * default {@link MultiThreadedHttpConnectionManager}. + */ + public CommonsClientHttpRequestFactory() { + httpClient = new HttpClient(new MultiThreadedHttpConnectionManager()); + this.setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS); + } + + /** + * Create a new instance of the CommonsHttpRequestFactory with the given {@link HttpClient} instance. + * + * @param httpClient the HttpClient instance to use for this sender + */ + public CommonsClientHttpRequestFactory(HttpClient httpClient) { + Assert.notNull(httpClient, "httpClient must not be null"); + this.httpClient = httpClient; + } + + /** + * Returns the HttpClient used by this message sender. + */ + public HttpClient getHttpClient() { + return httpClient; + } + + /** + * Set the HttpClient used by this message sender. + */ + public void setHttpClient(HttpClient httpClient) { + this.httpClient = httpClient; + } + + /** + * Set the socket read timeout for the underlying HttpClient. A value of 0 means never timeout. + * + * @param timeout the timeout value in milliseconds + * @see org.apache.commons.httpclient.params.HttpConnectionManagerParams#setSoTimeout(int) + */ + public void setReadTimeout(int timeout) { + if (timeout < 0) { + throw new IllegalArgumentException("timeout must be a non-negative value"); + } + this.httpClient.getHttpConnectionManager().getParams().setSoTimeout(timeout); + } + + public void destroy() throws Exception { + HttpConnectionManager connectionManager = getHttpClient().getHttpConnectionManager(); + if (connectionManager instanceof MultiThreadedHttpConnectionManager) { + ((MultiThreadedHttpConnectionManager) connectionManager).shutdown(); + } + } + + public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { + String uriString = uri.toString(); + HttpMethodBase httpMethodBase; + switch (httpMethod) { + case GET: + httpMethodBase = new GetMethod(uriString); + break; + case DELETE: + httpMethodBase = new DeleteMethod(uriString); + break; + case HEAD: + httpMethodBase = new HeadMethod(uriString); + break; + case OPTIONS: + httpMethodBase = new OptionsMethod(uriString); + break; + case POST: + httpMethodBase = new PostMethod(uriString); + break; + case PUT: + httpMethodBase = new PutMethod(uriString); + break; + case TRACE: + httpMethodBase = new TraceMethod(uriString); + break; + default: + throw new IllegalArgumentException("Invalid method: " + httpMethod); + } + process(httpMethodBase); + + return new CommonsClientHttpRequest(getHttpClient(), httpMethodBase); + } + + /** + * Template method that allows for manipulating the {@link org.apache.commons.httpclient.HttpMethodBase} before it is + * returned as part of a {@link CommonsClientHttpRequest}.

Default implementation is empty. + * + * @param httpMethod the Commons HTTP method to process + */ + protected void process(HttpMethodBase httpMethod) { + } +} \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/client/commons/CommonsClientHttpResponse.java b/org.springframework.web/src/main/java/org/springframework/web/http/client/commons/CommonsClientHttpResponse.java new file mode 100644 index 0000000000..52be8c634e --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/client/commons/CommonsClientHttpResponse.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http.client.commons; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpMethod; + +import org.springframework.web.http.HttpHeaders; +import org.springframework.web.http.HttpStatus; +import org.springframework.web.http.client.ClientHttpResponse; + +/** + * {@link org.springframework.web.http.client.ClientHttpResponse} implementation that uses Commons Http Client to + * execute requests. Created via the {@link CommonsClientHttpRequest}. + * + * @author Arjen Poutsma + * @see CommonsClientHttpRequest#execute() + */ +final class CommonsClientHttpResponse implements ClientHttpResponse { + + private final HttpMethod httpMethod; + + private HttpHeaders headers; + + CommonsClientHttpResponse(HttpMethod httpMethod) { + this.httpMethod = httpMethod; + } + + public HttpStatus getStatusCode() { + return HttpStatus.valueOf(httpMethod.getStatusCode()); + } + + public String getStatusText() { + return httpMethod.getStatusText(); + } + + public HttpHeaders getHeaders() { + if (headers == null) { + headers = new HttpHeaders(); + for (Header header : httpMethod.getResponseHeaders()) { + headers.add(header.getName(), header.getValue()); + } + } + return headers; + } + + public InputStream getBody() throws IOException { + return httpMethod.getResponseBodyAsStream(); + } + + public void close() { + httpMethod.releaseConnection(); + } + +} \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/client/commons/package.html b/org.springframework.web/src/main/java/org/springframework/web/http/client/commons/package.html new file mode 100644 index 0000000000..77dfb3e324 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/client/commons/package.html @@ -0,0 +1,8 @@ + + + +Contains an implementation of the ClientHttpRequest and +ClientHttpResponse based on Commons HTTP Client. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/client/package.html b/org.springframework.web/src/main/java/org/springframework/web/http/client/package.html new file mode 100644 index 0000000000..4dd05115df --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/client/package.html @@ -0,0 +1,10 @@ + + + +Contains an abstraction over client-side HTTP. This package +contains the ClientHttpRequest and +ClientHttpResponse, as well as a basic implementation of these +interfaces. + + + diff --git a/org.springframework.web/src/main/java/org/springframework/web/http/package.html b/org.springframework.web/src/main/java/org/springframework/web/http/package.html new file mode 100644 index 0000000000..4dea37deb3 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/http/package.html @@ -0,0 +1,8 @@ + + + +Contains a basic abstraction over client/server-side HTTP. This package +contains the HttpInputMessage and HttpOutputMessage. + + + diff --git a/org.springframework.web/src/test/java/org/springframework/web/http/HttpHeadersTests.java b/org.springframework.web/src/test/java/org/springframework/web/http/HttpHeadersTests.java new file mode 100644 index 0000000000..9d2bc4e1ea --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/web/http/HttpHeadersTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.util.MediaType; + +/** + * @author Arjen Poutsma + */ +public class HttpHeadersTests { + + private HttpHeaders headers; + + @Before + public void setUp() { + headers = new HttpHeaders(); + } + + @Test + public void accept() { + MediaType mediaType1 = new MediaType("text", "html"); + MediaType mediaType2 = new MediaType("text", "plain"); + List mediaTypes = new ArrayList(2); + mediaTypes.add(mediaType1); + mediaTypes.add(mediaType2); + headers.setAccept(mediaTypes); + assertEquals("Invalid Accept header", mediaTypes, headers.getAccept()); + assertEquals("Invalid Accept header", "text/html,text/plain", headers.getFirst("Accept")); + } + + @Test + public void allow() { + EnumSet methods = EnumSet.of(HttpMethod.GET, HttpMethod.POST); + headers.setAllow(methods); + assertEquals("Invalid Allow header", methods, headers.getAllow()); + assertEquals("Invalid Allow header", "GET,POST", headers.getFirst("Allow")); + } + + @Test + public void contentLength() { + long length = 42L; + headers.setContentLength(length); + assertEquals("Invalid Content-Length header", length, headers.getContentLength()); + assertEquals("Invalid Content-Length header", "42", headers.getFirst("Content-Length")); + } + + @Test + public void contentType() { + MediaType contentType = new MediaType("text", "html", Charset.forName("UTF-8")); + headers.setContentType(contentType); + assertEquals("Invalid Content-Type header", contentType, headers.getContentType()); + assertEquals("Invalid Content-Type header", "text/html;charset=UTF-8", headers.getFirst("Content-Type")); + } + + @Test + public void location() throws URISyntaxException { + URI location = new URI("http://www.example.com/hotels"); + headers.setLocation(location); + assertEquals("Invalid Location header", location, headers.getLocation()); + assertEquals("Invalid Location header", "http://www.example.com/hotels", headers.getFirst("Location")); + } +} diff --git a/org.springframework.web/src/test/java/org/springframework/web/http/MockHttpInputMessage.java b/org.springframework.web/src/test/java/org/springframework/web/http/MockHttpInputMessage.java new file mode 100644 index 0000000000..828c1ddd6c --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/web/http/MockHttpInputMessage.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.util.Assert; + +/** + * @author Arjen Poutsma + */ +public class MockHttpInputMessage implements HttpInputMessage { + + private final HttpHeaders headers = new HttpHeaders(); + + private final InputStream body; + + public MockHttpInputMessage(byte[] contents) { + Assert.notNull(contents, "'contents' must not be null"); + this.body = new ByteArrayInputStream(contents); + } + + public MockHttpInputMessage(InputStream body) { + Assert.notNull(body, "'body' must not be null"); + this.body = body; + } + + public HttpHeaders getHeaders() { + return headers; + } + + public InputStream getBody() throws IOException { + return body; + } +} diff --git a/org.springframework.web/src/test/java/org/springframework/web/http/MockHttpOutputMessage.java b/org.springframework.web/src/test/java/org/springframework/web/http/MockHttpOutputMessage.java new file mode 100644 index 0000000000..844eea0343 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/web/http/MockHttpOutputMessage.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; + +/** + * @author Arjen Poutsma + */ +public class MockHttpOutputMessage implements HttpOutputMessage { + + private final HttpHeaders headers = new HttpHeaders(); + + private final ByteArrayOutputStream body = new ByteArrayOutputStream(); + + public HttpHeaders getHeaders() { + return headers; + } + + public OutputStream getBody() throws IOException { + return body; + } + + public byte[] getBodyAsBytes() { + return body.toByteArray(); + } + + public String getBodyAsString(Charset charset) { + byte[] bytes = getBodyAsBytes(); + return new String(bytes, charset); + } +} diff --git a/org.springframework.web/src/test/java/org/springframework/web/http/client/AbstractHttpRequestFactoryTestCase.java b/org.springframework.web/src/test/java/org/springframework/web/http/client/AbstractHttpRequestFactoryTestCase.java new file mode 100644 index 0000000000..002c86ff8d --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/web/http/client/AbstractHttpRequestFactoryTestCase.java @@ -0,0 +1,146 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http.client; + +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; +import java.util.Enumeration; +import javax.servlet.GenericServlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.AfterClass; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mortbay.jetty.Server; +import org.mortbay.jetty.servlet.Context; +import org.mortbay.jetty.servlet.ServletHolder; + +import org.springframework.util.FileCopyUtils; +import org.springframework.web.http.HttpMethod; +import org.springframework.web.http.HttpStatus; + +public abstract class AbstractHttpRequestFactoryTestCase { + + private ClientHttpRequestFactory factory; + + private static Server jettyServer; + + @BeforeClass + public static void startJettyServer() throws Exception { + jettyServer = new Server(8889); + Context jettyContext = new Context(jettyServer, "/"); + jettyContext.addServlet(new ServletHolder(new EchoServlet()), "/echo"); + jettyContext.addServlet(new ServletHolder(new ErrorServlet(404)), "/errors/notfound"); + jettyServer.start(); + } + + @Before + public final void createFactory() { + factory = createRequestFactory(); + } + + protected abstract ClientHttpRequestFactory createRequestFactory(); + + @AfterClass + public static void stopJettyServer() throws Exception { + if (jettyServer != null) { + jettyServer.stop(); + } + } + + @Test + public void status() throws Exception { + ClientHttpRequest request = + factory.createRequest(new URI("http://localhost:8889/errors/notfound"), HttpMethod.GET); + assertEquals("Invalid HTTP method", HttpMethod.GET, request.getMethod()); + ClientHttpResponse response = request.execute(); + assertEquals("Invalid status code", HttpStatus.NOT_FOUND, response.getStatusCode()); + } + + @Test + public void echo() throws Exception { + ClientHttpRequest request = factory.createRequest(new URI("http://localhost:8889/echo"), HttpMethod.PUT); + assertEquals("Invalid HTTP method", HttpMethod.PUT, request.getMethod()); + String headerName = "MyHeader"; + String headerValue1 = "value1"; + request.getHeaders().add(headerName, headerValue1); + String headerValue2 = "value2"; + request.getHeaders().add(headerName, headerValue2); + byte[] body = "Hello World".getBytes("UTF-8"); + FileCopyUtils.copy(body, request.getBody()); + ClientHttpResponse response = request.execute(); + assertEquals("Invalid status code", HttpStatus.OK, response.getStatusCode()); + assertTrue("Header not found", response.getHeaders().containsKey(headerName)); + assertEquals("Header value not found", Arrays.asList(headerValue1, headerValue2), + response.getHeaders().get(headerName)); + byte[] result = FileCopyUtils.copyToByteArray(response.getBody()); + assertTrue("Invalid body", Arrays.equals(body, result)); + } + + /** + * Servlet that returns and error message for a given status code. + */ + private static class ErrorServlet extends GenericServlet { + + private final int sc; + + private ErrorServlet(int sc) { + this.sc = sc; + } + + @Override + public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { + ((HttpServletResponse) response).sendError(sc); + } + } + + private static class EchoServlet extends HttpServlet { + + @Override + protected void doPut(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + echo(request, response); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + echo(request, response); + } + + private void echo(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setStatus(HttpServletResponse.SC_OK); + for (Enumeration e1 = request.getHeaderNames(); e1.hasMoreElements();) { + String headerName = (String) e1.nextElement(); + for (Enumeration e2 = request.getHeaders(headerName); e2.hasMoreElements();) { + String headerValue = (String) e2.nextElement(); + response.addHeader(headerName, headerValue); + } + } + FileCopyUtils.copy(request.getInputStream(), response.getOutputStream()); + } + } + +} \ No newline at end of file diff --git a/org.springframework.web/src/test/java/org/springframework/web/http/client/SimpleHttpRequestFactoryTests.java b/org.springframework.web/src/test/java/org/springframework/web/http/client/SimpleHttpRequestFactoryTests.java new file mode 100644 index 0000000000..544de2bf55 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/web/http/client/SimpleHttpRequestFactoryTests.java @@ -0,0 +1,25 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http.client; + +public class SimpleHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase { + + @Override + protected ClientHttpRequestFactory createRequestFactory() { + return new SimpleClientHttpRequestFactory(); + } +} diff --git a/org.springframework.web/src/test/java/org/springframework/web/http/client/commons/CommonsHttpRequestFactoryTest.java b/org.springframework.web/src/test/java/org/springframework/web/http/client/commons/CommonsHttpRequestFactoryTest.java new file mode 100644 index 0000000000..c7775992fc --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/web/http/client/commons/CommonsHttpRequestFactoryTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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. + */ + +package org.springframework.web.http.client.commons; + +import org.springframework.web.http.client.AbstractHttpRequestFactoryTestCase; +import org.springframework.web.http.client.ClientHttpRequestFactory; + +public class CommonsHttpRequestFactoryTest extends AbstractHttpRequestFactoryTestCase { + + @Override + protected ClientHttpRequestFactory createRequestFactory() { + return new CommonsClientHttpRequestFactory(); + } +} \ No newline at end of file diff --git a/org.springframework.web/web.iml b/org.springframework.web/web.iml index 29d2ec8195..6310bd0183 100644 --- a/org.springframework.web/web.iml +++ b/org.springframework.web/web.iml @@ -1,177 +1,232 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +