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:
+ *
- {@link #getFirst(String)} returns the first value associated with a given header name
- {@link
+ * #add(String, String)} adds a header value to the list of values for a header name
- {@link #set(String,
+ * String)} sets the header value to a single string value
+ *
+ * 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 extends String, ? extends List> 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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+