├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.codehaus.groovy.eclipse.preferences.prefs ├── org.eclipse.jdt.core.prefs └── org.eclipse.mylyn.tasks.ui.prefs ├── README ├── pom.xml └── src ├── .gitignore ├── main ├── assembly │ └── all.xml ├── java │ └── groovyx │ │ └── net │ │ └── http │ │ ├── AsyncHTTPBuilder.java │ │ ├── AuthConfig.java │ │ ├── ContentEncoding.java │ │ ├── ContentEncodingRegistry.java │ │ ├── ContentType.java │ │ ├── DeflateEncoding.java │ │ ├── EncoderRegistry.java │ │ ├── GZIPEncoding.java │ │ ├── HTTPBuilder.java │ │ ├── HttpContextDecorator.java │ │ ├── HttpResponseDecorator.java │ │ ├── HttpResponseException.java │ │ ├── HttpURLClient.java │ │ ├── Method.java │ │ ├── ParserRegistry.java │ │ ├── RESTClient.java │ │ ├── ResponseParseException.java │ │ ├── Status.java │ │ ├── StringHashMap.java │ │ ├── URIBuilder.java │ │ ├── package.html │ │ └── thirdparty │ │ ├── GAEClientConnection.java │ │ └── GAEConnectionManager.java ├── resources │ └── catalog │ │ ├── HTMLlat1.ent │ │ ├── HTMLspecial.ent │ │ ├── HTMLsymbol.ent │ │ ├── frameset.dtd │ │ ├── html.xml │ │ ├── loose.dtd │ │ ├── strict.dtd │ │ ├── xhtml-lat1.ent │ │ ├── xhtml-special.ent │ │ ├── xhtml-symbol.ent │ │ ├── xhtml1-frameset.dtd │ │ ├── xhtml1-strict.dtd │ │ ├── xhtml1-transitional.dtd │ │ ├── xhtml11-flat.dtd │ │ └── xhtml11.dtd └── script │ ├── release_tweet.groovy │ └── twitter_restbuilder.groovy ├── site ├── apt │ ├── changes.apt │ ├── contrib.apt │ ├── doc │ │ ├── async.apt │ │ ├── auth.apt │ │ ├── contentTypes.apt │ │ ├── get.apt │ │ ├── handlers.apt │ │ ├── httpurlclient.apt │ │ ├── index.apt │ │ ├── json.apt │ │ ├── post.apt │ │ ├── rest.apt │ │ ├── ssl.apt │ │ ├── uribuilder.apt │ │ └── xml.apt │ ├── download.apt.vm │ ├── home.apt │ └── index.apt ├── examples.txt ├── resources │ └── images │ │ ├── logo.png │ │ └── logo.svg ├── site.xml └── xdoc │ └── about.xml └── test ├── groovy └── groovyx │ └── net │ └── http │ ├── AsyncHTTPBuilderTest.groovy │ ├── HTTPBuilderTest.groovy │ ├── HttpURLClientTest.groovy │ ├── RESTClientTest.groovy │ ├── RegistryTest.groovy │ ├── SSLTest.groovy │ ├── ServerTest.groovy │ ├── URIBuilderTest.groovy │ └── thirdparty │ └── GAETest.groovy └── resources ├── log4j.xml ├── rss-catalog.xml ├── rss_1_0_validator.xml └── truststore.jks /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *.iml 3 | .idea/ 4 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTTPBuilder 4 | 5 | 6 | 7 | 8 | 9 | org.maven.ide.eclipse.maven2Builder 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.codehaus.groovy.eclipse.groovyBuilder 20 | 21 | 22 | 23 | 24 | 25 | org.eclipse.jdt.core.javanature 26 | org.maven.ide.eclipse.maven2Nature 27 | org.codehaus.groovy.eclipse.groovyNature 28 | 29 | 30 | -------------------------------------------------------------------------------- /.settings/org.codehaus.groovy.eclipse.preferences.prefs: -------------------------------------------------------------------------------- 1 | #Fri Dec 19 20:51:50 EST 2008 2 | eclipse.preferences.version=1 3 | groovy.compiler.output.path=bin/groovy 4 | support.groovy=true 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | #Mon Nov 24 14:58:58 EST 2008 2 | eclipse.preferences.version=1 3 | org.eclipse.jdt.core.builder.resourceCopyExclusionFilter=*.launch,*.groovy 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 5 | org.eclipse.jdt.core.compiler.compliance=1.6 6 | org.eclipse.jdt.core.compiler.source=1.6 7 | -------------------------------------------------------------------------------- /.settings/org.eclipse.mylyn.tasks.ui.prefs: -------------------------------------------------------------------------------- 1 | #Thu Dec 18 17:01:03 EST 2008 2 | eclipse.preferences.version=1 3 | project.repository.kind=jira 4 | project.repository.url=http\://jira.codehaus.org 5 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | HTTPBuilder = Easy HTTP client for Groovy. 2 | 3 | Build Instructions: 4 | 5 | HTTPBuilder is built using Apache Maven (http://maven.apache.org) 6 | $ mvn install 7 | 8 | Documentation: https://github.com/jgritman/httpbuilder/wiki 9 | 10 | Contributions: 11 | 12 | This project relies on the work of many open source projects including: 13 | * Groovy: http://groovy.codehaus.org 14 | * Apache HttpClient: http://hc.apache.org 15 | * Json-Lib: http://json-lib.sourceforge.net/ 16 | * Neko HTML: http://nekohtml.sourceforge.net/ 17 | * Signpost: http://code.google.com/p/oauth-signpost/ 18 | 19 | This project also includes source code written by Martin Blom (martin@blom.org) 20 | in the 'thirdparty' package. It is licensed under the LGPL v3 and 21 | re-distributed with permission from the author. 22 | 23 | 24 | License: 25 | 26 | HTTPBuilder is copyright 2009-2011 Thomas Nichols except where otherwise noted. 27 | 28 | This project is licensed under the Apache License Version 2.0 except where 29 | otherwise noted in the source files. 30 | 31 | You are receiving this code free of charge, which represents many hours of 32 | effort from other individuals and corporations. As a responsible member 33 | of the community, you are encouraged (but not required) to donate any 34 | enhancements or improvements back to the community under a similar open 35 | source license. Thank you. -TMN 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /src/main/assembly/all.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | all 6 | 7 | tar.gz 8 | zip 9 | 10 | 11 | 12 | 13 | . 14 | 15 | 16 | pom.xml 17 | src/** 18 | 19 | 20 | 21 | target 22 | 23 | 24 | *.jar 25 | site/** 26 | 27 | 28 | 29 | 30 | 31 | 32 | false 33 | runtime 34 | false 35 | false 36 | dependencies 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/AsyncHTTPBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | import java.io.IOException; 25 | import java.net.URISyntaxException; 26 | import java.util.Map; 27 | import java.util.concurrent.Callable; 28 | import java.util.concurrent.ExecutionException; 29 | import java.util.concurrent.ExecutorService; 30 | import java.util.concurrent.Future; 31 | import java.util.concurrent.LinkedBlockingQueue; 32 | import java.util.concurrent.ThreadPoolExecutor; 33 | import java.util.concurrent.TimeUnit; 34 | 35 | import org.apache.http.HttpVersion; 36 | import org.apache.http.conn.ClientConnectionManager; 37 | import org.apache.http.conn.params.ConnManagerParams; 38 | import org.apache.http.conn.params.ConnPerRouteBean; 39 | import org.apache.http.conn.scheme.PlainSocketFactory; 40 | import org.apache.http.conn.scheme.Scheme; 41 | import org.apache.http.conn.scheme.SchemeRegistry; 42 | import org.apache.http.conn.ssl.SSLSocketFactory; 43 | import org.apache.http.impl.client.DefaultHttpClient; 44 | import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; 45 | import org.apache.http.params.BasicHttpParams; 46 | import org.apache.http.params.HttpConnectionParams; 47 | import org.apache.http.params.HttpParams; 48 | import org.apache.http.params.HttpProtocolParams; 49 | 50 | /** 51 | * This implementation makes all requests asynchronous by submitting jobs to a 52 | * {@link ThreadPoolExecutor}. All request methods (including get 53 | * and post) return a {@link Future} instance, whose 54 | * {@link Future#get() get} method will provide access to whatever value was 55 | * returned from the response handler closure. 56 | * 57 | * @author Tom Nichols 58 | */ 59 | public class AsyncHTTPBuilder extends HTTPBuilder { 60 | 61 | /** 62 | * Default pool size is one is not supplied in the constructor. 63 | */ 64 | public static final int DEFAULT_POOL_SIZE = 4; 65 | 66 | protected ExecutorService threadPool; 67 | // = (ThreadPoolExecutor)Executors.newCachedThreadPool(); 68 | 69 | /** 70 | * Accepts the following named parameters: 71 | *
72 | *
threadPool
Custom {@link ExecutorService} instance for 73 | * running submitted requests. If this is an instance of {@link ThreadPoolExecutor}, 74 | * the poolSize will be determined by {@link ThreadPoolExecutor#getMaximumPoolSize()}. 75 | * The default threadPool uses an unbounded queue to accept an unlimited 76 | * number of requests.
77 | *
poolSize
Max number of concurrent requests
78 | *
uri
Default request URI
79 | *
contentType
Default content type for requests and responses
80 | *
timeout
Timeout in milliseconds to wait for a connection to 81 | * be established and request to complete.
82 | *
83 | */ 84 | public AsyncHTTPBuilder( Map args ) throws URISyntaxException { 85 | int poolSize = DEFAULT_POOL_SIZE; 86 | ExecutorService threadPool = null; 87 | if ( args != null ) { 88 | threadPool = (ExecutorService)args.remove( "threadPool" ); 89 | 90 | if ( threadPool instanceof ThreadPoolExecutor ) 91 | poolSize = ((ThreadPoolExecutor)threadPool).getMaximumPoolSize(); 92 | 93 | Object poolSzArg = args.remove("poolSize"); 94 | if ( poolSzArg != null ) poolSize = Integer.parseInt( poolSzArg.toString() ); 95 | 96 | if ( args.containsKey( "url" ) ) throw new IllegalArgumentException( 97 | "The 'url' parameter is deprecated; use 'uri' instead" ); 98 | Object defaultURI = args.remove("uri"); 99 | if ( defaultURI != null ) super.setUri(defaultURI); 100 | 101 | Object defaultContentType = args.remove("contentType"); 102 | if ( defaultContentType != null ) 103 | super.setContentType(defaultContentType); 104 | 105 | Object timeout = args.remove( "timeout" ); 106 | if ( timeout != null ) setTimeout( (Integer) timeout ); 107 | 108 | if ( args.size() > 0 ) { 109 | String invalidArgs = ""; 110 | for ( String k : args.keySet() ) invalidArgs += k + ","; 111 | throw new IllegalArgumentException("Unexpected keyword args: " + invalidArgs); 112 | } 113 | } 114 | this.initThreadPools( poolSize, threadPool ); 115 | } 116 | 117 | /** 118 | * Submits a {@link Callable} instance to the job pool, which in turn will 119 | * call {@link HTTPBuilder#doRequest(RequestConfigDelegate)} in an asynchronous 120 | * thread. The {@link Future} instance returned by this value (which in 121 | * turn should be returned by any of the public request methods 122 | * (including get and post) may be used to 123 | * retrieve whatever value may be returned from the executed response 124 | * handler closure. 125 | */ 126 | @Override 127 | protected Future doRequest( final RequestConfigDelegate delegate ) { 128 | return threadPool.submit( new Callable() { 129 | /*@Override*/ public Object call() throws Exception { 130 | try { 131 | return doRequestSuper(delegate); 132 | } 133 | catch( Exception ex ) { 134 | log.info( "Exception thrown from response delegate: " + delegate, ex ); 135 | throw ex; 136 | } 137 | } 138 | }); 139 | } 140 | 141 | /* 142 | * Because we can't call "super.doRequest" from within the anonymous 143 | * Callable subclass. 144 | */ 145 | private Object doRequestSuper( RequestConfigDelegate delegate ) throws IOException { 146 | return super.doRequest(delegate); 147 | } 148 | 149 | /** 150 | * Initializes threading parameters for the HTTPClient's 151 | * {@link ThreadSafeClientConnManager}, and this class' ThreadPoolExecutor. 152 | */ 153 | protected void initThreadPools( final int poolSize, final ExecutorService threadPool ) { 154 | if (poolSize < 1) throw new IllegalArgumentException("poolSize may not be < 1"); 155 | // Create and initialize HTTP parameters 156 | HttpParams params = super.getClient().getParams(); 157 | ConnManagerParams.setMaxTotalConnections(params, poolSize); 158 | ConnManagerParams.setMaxConnectionsPerRoute(params, 159 | new ConnPerRouteBean(poolSize)); 160 | 161 | HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); 162 | 163 | // Create and initialize scheme registry 164 | SchemeRegistry schemeRegistry = new SchemeRegistry(); 165 | schemeRegistry.register( new Scheme( "http", 166 | PlainSocketFactory.getSocketFactory(), 80 ) ); 167 | schemeRegistry.register( new Scheme( "https", 168 | SSLSocketFactory.getSocketFactory(), 443)); 169 | 170 | ClientConnectionManager cm = new ThreadSafeClientConnManager( 171 | params, schemeRegistry ); 172 | setClient(new DefaultHttpClient( cm, params )); 173 | 174 | this.threadPool = threadPool != null ? threadPool : 175 | new ThreadPoolExecutor( poolSize, poolSize, 120, TimeUnit.SECONDS, 176 | new LinkedBlockingQueue() ); 177 | } 178 | 179 | /** 180 | * {@inheritDoc} 181 | */ 182 | @Override 183 | protected Object defaultSuccessHandler( HttpResponseDecorator resp, Object parsedData ) 184 | throws ResponseParseException { 185 | return super.defaultSuccessHandler( resp, parsedData ); 186 | } 187 | 188 | /** 189 | * For 'failure' responses (e.g. a 404), the exception will be wrapped in 190 | * a {@link ExecutionException} and held by the {@link Future} instance. 191 | * The exception is then re-thrown when calling {@link Future#get() 192 | * future.get()}. You can access the original exception (e.g. an 193 | * {@link HttpResponseException}) by calling ex.getCause(). 194 | * 195 | */ 196 | @Override 197 | protected void defaultFailureHandler( HttpResponseDecorator resp ) 198 | throws HttpResponseException { 199 | super.defaultFailureHandler( resp ); 200 | } 201 | 202 | /** 203 | * This timeout is used for both the time to wait for an established 204 | * connection, and the time to wait for data. 205 | * @see HttpConnectionParams#setSoTimeout(HttpParams, int) 206 | * @see HttpConnectionParams#setConnectionTimeout(HttpParams, int) 207 | * @param timeout time to wait in milliseconds. 208 | */ 209 | public void setTimeout( int timeout ) { 210 | HttpConnectionParams.setConnectionTimeout( super.getClient().getParams(), timeout ); 211 | HttpConnectionParams.setSoTimeout( super.getClient().getParams(), timeout ); 212 | /* this will cause a thread waiting for an available connection instance 213 | * to time-out */ 214 | // ConnManagerParams.setTimeout( super.getClient().getParams(), timeout ); 215 | } 216 | 217 | /** 218 | * Get the timeout in for establishing an HTTP connection. 219 | * @return timeout in milliseconds. 220 | */ 221 | public int getTimeout() { 222 | return HttpConnectionParams.getConnectionTimeout( super.getClient().getParams() ); 223 | } 224 | 225 | /** 226 | *

Access the underlying threadpool to adjust things like job timeouts.

227 | * 228 | *

Note that this is not the same pool used by the HttpClient's 229 | * {@link ThreadSafeClientConnManager}. Therefore, increasing the 230 | * {@link ThreadPoolExecutor#setMaximumPoolSize(int) maximum pool size} will 231 | * not in turn increase the number of possible concurrent requests. It will 232 | * simply cause more requests to be attempted which will then simply 233 | * block while waiting for a free connection.

234 | * 235 | * @return the service used to execute requests. By default this is a 236 | * {@link ThreadPoolExecutor}. 237 | */ 238 | public ExecutorService getThreadExecutor() { 239 | return this.threadPool; 240 | } 241 | 242 | /** 243 | * {@inheritDoc} 244 | */ 245 | @Override public void shutdown() { 246 | super.shutdown(); 247 | this.threadPool.shutdown(); 248 | } 249 | 250 | /** 251 | * {@inheritDoc} 252 | * @see #shutdown() 253 | */ 254 | @Override protected void finalize() throws Throwable { 255 | this.shutdown(); 256 | super.finalize(); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/AuthConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.net.URI; 27 | import java.net.URISyntaxException; 28 | import java.net.URL; 29 | import java.security.GeneralSecurityException; 30 | import java.security.KeyStore; 31 | import java.util.HashMap; 32 | import java.util.Map; 33 | 34 | import oauth.signpost.OAuthConsumer; 35 | import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer; 36 | import oauth.signpost.exception.OAuthException; 37 | 38 | import org.apache.http.Header; 39 | import org.apache.http.HttpEntityEnclosingRequest; 40 | import org.apache.http.HttpException; 41 | import org.apache.http.HttpHost; 42 | import org.apache.http.HttpRequest; 43 | import org.apache.http.HttpRequestInterceptor; 44 | import org.apache.http.auth.AuthScope; 45 | import org.apache.http.auth.NTCredentials; 46 | import org.apache.http.auth.UsernamePasswordCredentials; 47 | import org.apache.http.client.HttpClient; 48 | import org.apache.http.conn.scheme.Scheme; 49 | import org.apache.http.conn.ssl.SSLSocketFactory; 50 | import org.apache.http.impl.client.AbstractHttpClient; 51 | import org.apache.http.protocol.ExecutionContext; 52 | import org.apache.http.protocol.HttpContext; 53 | 54 | /** 55 | * Encapsulates all configuration related to HTTP authentication methods. 56 | * @see HTTPBuilder#getAuth() 57 | * 58 | * @author Tom Nichols 59 | */ 60 | public class AuthConfig { 61 | protected HTTPBuilder builder; 62 | public AuthConfig( HTTPBuilder builder ) { 63 | this.builder = builder; 64 | } 65 | 66 | /** 67 | * Set authentication credentials to be used for the current 68 | * {@link HTTPBuilder#getUri() default host}. This method name is a bit of 69 | * a misnomer, since these credentials will actually work for "digest" 70 | * authentication as well. 71 | * @param user 72 | * @param pass 73 | */ 74 | public void basic( String user, String pass ) { 75 | URI uri = ((URIBuilder)builder.getUri()).toURI(); 76 | if ( uri == null ) throw new IllegalStateException( "a default URI must be set" ); 77 | this.basic( uri.getHost(), uri.getPort(), user, pass ); 78 | } 79 | 80 | /** 81 | * Set authentication credentials to be used for the given host and port. 82 | * @param host 83 | * @param port 84 | * @param user 85 | * @param pass 86 | */ 87 | public void basic( String host, int port, String user, String pass ) { 88 | final HttpClient client = builder.getClient(); 89 | if ( !(client instanceof AbstractHttpClient )) { 90 | throw new IllegalStateException("client is not an AbstractHttpClient"); 91 | } 92 | ((AbstractHttpClient)client).getCredentialsProvider().setCredentials( 93 | new AuthScope( host, port ), 94 | new UsernamePasswordCredentials( user, pass ) 95 | ); 96 | } 97 | 98 | /** 99 | * Set NTLM authentication credentials to be used for the current 100 | * {@link HTTPBuilder#getUri() default host}. 101 | * @param user 102 | * @param pass 103 | * @param workstation 104 | * @param domain 105 | */ 106 | public void ntlm( String user, String pass, String workstation, String domain ) { 107 | URI uri = ((URIBuilder)builder.getUri()).toURI(); 108 | if ( uri == null ) throw new IllegalStateException( "a default URI must be set" ); 109 | this.ntlm( uri.getHost(), uri.getPort(), user, pass, workstation, domain ); 110 | } 111 | 112 | /** 113 | * Set NTLM authentication credentials to be used for the given host and port. 114 | * @param host 115 | * @param port 116 | * @param user 117 | * @param pass 118 | * @param workstation 119 | * @param domain 120 | */ 121 | public void ntlm( String host, int port, String user, String pass, String workstation, String domain ) { 122 | final HttpClient client = builder.getClient(); 123 | if ( !(client instanceof AbstractHttpClient )) { 124 | throw new IllegalStateException("client is not an AbstractHttpClient"); 125 | } 126 | ((AbstractHttpClient)client).getCredentialsProvider().setCredentials( 127 | new AuthScope( host, port ), 128 | new NTCredentials( user, pass, workstation, domain ) 129 | ); 130 | } 131 | 132 | /** 133 | * Sets a certificate to be used for SSL authentication. See 134 | * {@link Class#getResource(String)} for how to get a URL from a resource 135 | * on the classpath. 136 | * @param certURL URL to a JKS keystore where the certificate is stored. 137 | * @param password password to decrypt the keystore 138 | */ 139 | public void certificate( String certURL, String password ) 140 | throws GeneralSecurityException, IOException { 141 | 142 | KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType() ); 143 | InputStream jksStream = new URL(certURL).openStream(); 144 | try { 145 | keyStore.load( jksStream, password.toCharArray() ); 146 | } finally { jksStream.close(); } 147 | 148 | SSLSocketFactory ssl = new SSLSocketFactory(keyStore, password); 149 | ssl.setHostnameVerifier( SSLSocketFactory.STRICT_HOSTNAME_VERIFIER ); 150 | 151 | builder.getClient().getConnectionManager().getSchemeRegistry() 152 | .register( new Scheme("https", ssl, 443) ); 153 | } 154 | 155 | /** 156 | *

OAuth sign all requests. Note that this currently does not 157 | * wait for a WWW-Authenticate challenge before sending the 158 | * the OAuth header. All requests to all domains will be signed for this 159 | * instance.

160 | * 161 | *

This assumes you've already generated an accessToken and 162 | * secretToken for the site you're targeting. For More information 163 | * on how to achieve this, see the 164 | * Signpost documentation.

165 | * @since 0.5.1 166 | * @param consumerKey null if you want to unset 167 | * OAuth handling and stop signing requests. 168 | * @param consumerSecret 169 | * @param accessToken 170 | * @param secretToken 171 | */ 172 | public void oauth( String consumerKey, String consumerSecret, 173 | String accessToken, String secretToken ) { 174 | final HttpClient client = builder.getClient(); 175 | if ( !(client instanceof AbstractHttpClient )) { 176 | throw new IllegalStateException("client is not an AbstractHttpClient"); 177 | } 178 | ((AbstractHttpClient)client).removeRequestInterceptorByClass( OAuthSigner.class ); 179 | if ( consumerKey != null ) 180 | ((AbstractHttpClient)client).addRequestInterceptor( new OAuthSigner( 181 | consumerKey, consumerSecret, accessToken, secretToken ) ); 182 | } 183 | 184 | /** 185 | * This class is used to sign all requests via an {@link HttpRequestInterceptor} 186 | * until the context-aware AuthScheme is released in HttpClient 4.1. 187 | * @since 0.5.1 188 | */ 189 | static class OAuthSigner implements HttpRequestInterceptor { 190 | protected OAuthConsumer oauth; 191 | public OAuthSigner( String consumerKey, String consumerSecret, 192 | String accessToken, String secretToken ) { 193 | this.oauth = new CommonsHttpOAuthConsumer( consumerKey, consumerSecret ); 194 | oauth.setTokenWithSecret( accessToken, secretToken ); 195 | } 196 | 197 | public void process(HttpRequest request, HttpContext ctx) throws HttpException, IOException { 198 | /* The full request URI must be reconstructed between the context and the request URI. 199 | * Best we can do until AuthScheme supports HttpContext. See: 200 | * https://issues.apache.org/jira/browse/HTTPCLIENT-901 */ 201 | try { 202 | HttpHost host = (HttpHost) ctx.getAttribute( ExecutionContext.HTTP_TARGET_HOST ); 203 | final URI requestURI = new URI( host.toURI() ).resolve( request.getRequestLine().getUri() ); 204 | 205 | oauth.signpost.http.HttpRequest oAuthRequest = 206 | new OAuthRequestAdapter(request, requestURI); 207 | this.oauth.sign( oAuthRequest ); 208 | } 209 | catch ( URISyntaxException ex ) { 210 | throw new HttpException( "Error rebuilding request URI", ex ); 211 | } 212 | catch (OAuthException e) { 213 | throw new HttpException( "OAuth signing error", e); 214 | } 215 | } 216 | 217 | static class OAuthRequestAdapter implements oauth.signpost.http.HttpRequest { 218 | 219 | final HttpRequest request; 220 | final URI requestURI; 221 | OAuthRequestAdapter( HttpRequest request, URI requestURI ) { 222 | this.request = request; 223 | this.requestURI = requestURI; 224 | } 225 | 226 | public String getRequestUrl() { return requestURI.toString(); } 227 | public void setRequestUrl(String url) {/*ignore*/} 228 | public Map getAllHeaders() { 229 | Map headers = new HashMap(); 230 | // FIXME this doesn't account for repeated headers, 231 | // which are allowed by the HTTP spec!! 232 | for ( Header h : request.getAllHeaders() ) 233 | headers.put(h.getName(), h.getValue()); 234 | return headers; 235 | } 236 | public String getContentType() { 237 | try { 238 | return request.getFirstHeader("content-type").getValue(); 239 | } 240 | catch ( Exception ex ) { // NPE or ArrayOOBEx 241 | return null; 242 | } 243 | } 244 | public String getHeader(String name) { 245 | Header h = request.getFirstHeader(name); 246 | return h != null ? h.getValue() : null; 247 | } 248 | public InputStream getMessagePayload() throws IOException { 249 | if ( request instanceof HttpEntityEnclosingRequest ) 250 | return ((HttpEntityEnclosingRequest)request).getEntity().getContent(); 251 | return null; 252 | } 253 | public String getMethod() { 254 | return request.getRequestLine().getMethod(); 255 | } 256 | public void setHeader(String key, String val) { 257 | request.setHeader(key, val); 258 | } 259 | public Object unwrap() { 260 | return request; 261 | } 262 | }; 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/ContentEncoding.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | import java.io.IOException; 25 | 26 | import org.apache.http.Header; 27 | import org.apache.http.HeaderElement; 28 | import org.apache.http.HttpEntity; 29 | import org.apache.http.HttpException; 30 | import org.apache.http.HttpRequest; 31 | import org.apache.http.HttpRequestInterceptor; 32 | import org.apache.http.HttpResponse; 33 | import org.apache.http.HttpResponseInterceptor; 34 | import org.apache.http.protocol.HttpContext; 35 | 36 | /** 37 | * Base class for handing content-encoding. 38 | * @author Tom Nichols 39 | */ 40 | public abstract class ContentEncoding { 41 | 42 | public static final String ACCEPT_ENC_HDR = "Accept-Encoding"; 43 | public static final String CONTENT_ENC_HDR = "Content-Encoding"; 44 | 45 | protected abstract String getContentEncoding(); 46 | protected abstract HttpEntity wrapResponseEntity( HttpEntity raw ); 47 | 48 | public HttpRequestInterceptor getRequestInterceptor() { 49 | return new RequestInterceptor(); 50 | } 51 | 52 | public HttpResponseInterceptor getResponseInterceptor() { 53 | return new ResponseInterceptor(); 54 | } 55 | 56 | /** 57 | * Enumeration of common content-encodings. 58 | */ 59 | public static enum Type { 60 | GZIP, 61 | COMPRESS, 62 | DEFLATE; 63 | 64 | /** Prints the value as it should appear in an HTTP header */ 65 | @Override public String toString() { 66 | return this.name().toLowerCase(); 67 | } 68 | } 69 | 70 | /** 71 | * Request interceptor that adds the correct Accept header 72 | * to the outgoing request. 73 | * @author Tom Nichols 74 | */ 75 | protected class RequestInterceptor implements HttpRequestInterceptor { 76 | public void process( final HttpRequest req, 77 | final HttpContext context ) throws HttpException, IOException { 78 | 79 | // set the Accept-Encoding header: 80 | String encoding = getContentEncoding(); 81 | if ( !req.containsHeader( ACCEPT_ENC_HDR ) ) 82 | req.addHeader( ACCEPT_ENC_HDR, encoding ); 83 | 84 | else { 85 | StringBuilder values = new StringBuilder(); 86 | for ( Header h : req.getHeaders( ACCEPT_ENC_HDR ) ) 87 | values.append( h.getValue() ).append( "," ); 88 | 89 | String encList = (!values.toString().contains( encoding )) ? values 90 | .append( encoding ).toString() 91 | : values.toString().substring( 0, values.lastIndexOf( "," ) ); 92 | 93 | req.setHeader( ACCEPT_ENC_HDR, encList ); 94 | } 95 | 96 | //TODO compress request and add content-encoding header. 97 | } 98 | } 99 | 100 | /** 101 | * Response interceptor that filters the response stream to decode the 102 | * compressed content before it is passed on to the parser. 103 | * @author Tom Nichols 104 | */ 105 | protected class ResponseInterceptor implements HttpResponseInterceptor { 106 | public void process( final HttpResponse response, final HttpContext context ) 107 | throws HttpException, IOException { 108 | 109 | if ( hasEncoding( response, getContentEncoding() ) ) 110 | response.setEntity( wrapResponseEntity( response.getEntity() ) ); 111 | } 112 | 113 | protected boolean hasEncoding( final HttpResponse response, final String encoding ) { 114 | HttpEntity entity = response.getEntity(); 115 | if ( entity == null ) return false; 116 | Header ceHeader = entity.getContentEncoding(); 117 | if ( ceHeader == null ) return false; 118 | 119 | HeaderElement[] codecs = ceHeader.getElements(); 120 | for ( int i = 0; i < codecs.length; i++ ) 121 | if ( encoding.equalsIgnoreCase( codecs[i].getName() ) ) 122 | return true; 123 | 124 | return false; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/ContentEncodingRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | import groovyx.net.http.ContentEncoding.Type; 25 | 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | 29 | import org.apache.http.client.HttpClient; 30 | import org.apache.http.impl.client.AbstractHttpClient; 31 | 32 | /** 33 | * Keeps track of available content-encoding handlers. 34 | * @author Tom Nichols 35 | */ 36 | public class ContentEncodingRegistry { 37 | 38 | protected Map availableEncoders = getDefaultEncoders(); 39 | 40 | /** 41 | * This implementation adds a {@link GZIPEncoding} and {@link DeflateEncoding} 42 | * handler to the registry. Override this method to provide a different set 43 | * of defaults. 44 | * @return a map to content-encoding strings to {@link ContentEncoding} handlers. 45 | */ 46 | protected Map getDefaultEncoders() { 47 | Map map = new HashMap(); 48 | map.put( Type.GZIP.toString(), new GZIPEncoding() ); 49 | map.put( Type.DEFLATE.toString(), new DeflateEncoding() ); 50 | return map; 51 | } 52 | 53 | /** 54 | * Add the request and response interceptors to the {@link HttpClient}, 55 | * which will provide transparent decoding of the given content-encoding 56 | * types. This method is called by HTTPBuilder and probably should not need 57 | * be modified by sub-classes. 58 | * @param client client on which to set the request and response interceptors 59 | * @param encodings encoding name (either a {@link ContentEncoding.Type} or 60 | * a content-encoding string. 61 | */ 62 | void setInterceptors( final AbstractHttpClient client, Object... encodings ) { 63 | // remove any encoding interceptors that are already set 64 | client.removeRequestInterceptorByClass( ContentEncoding.RequestInterceptor.class ); 65 | client.removeResponseInterceptorByClass( ContentEncoding.ResponseInterceptor.class ); 66 | 67 | for ( Object encName : encodings ) { 68 | ContentEncoding enc = availableEncoders.get( encName.toString() ); 69 | if ( enc == null ) continue; 70 | client.addRequestInterceptor( enc.getRequestInterceptor() ); 71 | client.addResponseInterceptor( enc.getResponseInterceptor() ); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/ContentType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | import java.util.Iterator; 25 | 26 | import org.apache.commons.collections.iterators.ArrayIterator; 27 | 28 | /** 29 | * Enumeration of common IANA 30 | * content-types. This may be used to specify a request or response 31 | * content-type more easily than specifying the full string each time. i.e. 32 | *
33 |  * http.request( GET, JSON ) {...}
34 | * 35 | * Is roughly equivalent to: 36 | *
37 |  * http.request( GET, 'application/json' )
38 | * 39 | * The only difference being, equivalent content-types (i.e. 40 | * application/xml and text/xml are all added to the 41 | * request's Accept header. By default, all equivalent content-types 42 | * are handled the same by the {@link EncoderRegistry} and {@link ParserRegistry} 43 | * as well. 44 | * @author Tom Nichols 45 | */ 46 | public enum ContentType { 47 | /** */* */ 48 | ANY("*/*"), 49 | /** text/plain */ 50 | TEXT("text/plain"), 51 | /** 52 | *
    53 | *
  • application/json
  • 54 | *
  • application/javascript
  • 55 | *
  • text/javascript
  • 56 | *
57 | */ 58 | JSON("application/json","application/javascript","text/javascript"), 59 | /** 60 | *
    61 | *
  • application/xml
  • 62 | *
  • text/xml
  • 63 | *
  • application/xhtml+xml
  • 64 | *
  • application/atom+xml
  • 65 | *
66 | */ 67 | XML("application/xml","text/xml","application/xhtml+xml","application/atom+xml"), 68 | /** text/html */ 69 | HTML("text/html"), 70 | /** application/x-www-form-urlencoded */ 71 | URLENC("application/x-www-form-urlencoded"), 72 | /** application/octet-stream */ 73 | BINARY("application/octet-stream"); 74 | 75 | private final String[] ctStrings; 76 | public String[] getContentTypeStrings() { return ctStrings; } 77 | @Override public String toString() { return ctStrings[0]; } 78 | 79 | /** 80 | * Builds a string to be used as an HTTP Accept header 81 | * value, i.e. "application/xml, text/xml" 82 | * @return 83 | */ 84 | @SuppressWarnings("unchecked") 85 | public String getAcceptHeader() { 86 | Iterator iter = new ArrayIterator(ctStrings); 87 | StringBuilder sb = new StringBuilder(); 88 | while ( iter.hasNext() ) { 89 | sb.append( iter.next() ); 90 | if ( iter.hasNext() ) sb.append( ", " ); 91 | } 92 | return sb.toString(); 93 | } 94 | 95 | private ContentType( String... contentTypes ) { 96 | this.ctStrings = contentTypes; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/DeflateEncoding.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | import static groovyx.net.http.ContentEncoding.Type.DEFLATE; 25 | 26 | import org.apache.http.HttpEntity; 27 | import org.apache.http.client.entity.DeflateDecompressingEntity; 28 | 29 | /** 30 | * Content encoding used to handle Deflate responses. 31 | * @author Tom Nichols 32 | */ 33 | public class DeflateEncoding extends ContentEncoding { 34 | 35 | /** 36 | * Returns the {@link ContentEncoding.Type#DEFLATE} encoding string which is 37 | * added to the Accept-Encoding header by the base class. 38 | */ 39 | @Override 40 | public String getContentEncoding() { 41 | return DEFLATE.toString(); 42 | } 43 | 44 | 45 | /** 46 | * Wraps the raw entity in a {@link InflaterEntity}. 47 | */ 48 | @Override 49 | public HttpEntity wrapResponseEntity( HttpEntity raw ) { 50 | return new DeflateDecompressingEntity( raw ); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/GZIPEncoding.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | import static groovyx.net.http.ContentEncoding.Type.GZIP; 25 | 26 | import org.apache.http.HttpEntity; 27 | import org.apache.http.client.entity.GzipDecompressingEntity; 28 | 29 | /** 30 | * Content encoding used to handle GZIP responses. 31 | * @author Tom Nichols 32 | */ 33 | public class GZIPEncoding extends ContentEncoding { 34 | 35 | /** 36 | * Returns the {@link ContentEncoding.Type#GZIP} encoding string which is 37 | * added to the Accept-Encoding header by the base class. 38 | */ 39 | @Override 40 | public String getContentEncoding() { 41 | return GZIP.toString(); 42 | } 43 | 44 | /** 45 | * Wraps the raw entity in a {@link GZIPDecompressingEntity}. 46 | */ 47 | @Override 48 | public HttpEntity wrapResponseEntity( HttpEntity raw ) { 49 | return new GzipDecompressingEntity( raw ); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/HttpContextDecorator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | import org.apache.http.protocol.BasicHttpContext; 25 | import org.apache.http.protocol.HttpContext; 26 | 27 | /** 28 | * HttpContext stores many transient properties of an HTTP request. 29 | * This class adds Groovy convenience methods. For a list of many 30 | * common properties stored in the HttpContext, see: 31 | *
    32 | *
  • {@link org.apache.http.protocol.ExecutionContext}
  • 33 | *
  • {@link org.apache.http.client.protocol.ClientContext}
  • 34 | *
35 | * 36 | * @author tnichols 37 | */ 38 | public class HttpContextDecorator implements HttpContext { 39 | 40 | protected HttpContext delegate; 41 | 42 | public HttpContextDecorator() { 43 | this.delegate = new BasicHttpContext(); 44 | } 45 | 46 | public HttpContextDecorator( HttpContext delegate ) { 47 | this.delegate = new BasicHttpContext(delegate); 48 | } 49 | 50 | /** 51 | * Groovy support for the index [] operator 52 | * @param name 53 | * @return 54 | */ 55 | public Object getAt( String name ) { 56 | return this.getAttribute(name); 57 | } 58 | 59 | /** 60 | * Groovy support for the index [] operator 61 | * @param name 62 | * @param val 63 | */ 64 | public void setAt( String name, Object val ) { 65 | this.setAttribute(name, val); 66 | } 67 | 68 | /* (non-Javadoc) 69 | * @see org.apache.http.protocol.HttpContext#getAttribute(java.lang.String) 70 | */ 71 | public Object getAttribute(String name) { 72 | return this.delegate.getAttribute(name); 73 | } 74 | 75 | /* (non-Javadoc) 76 | * @see org.apache.http.protocol.HttpContext#removeAttribute(java.lang.String) 77 | */ 78 | public Object removeAttribute(String name) { 79 | return this.delegate.removeAttribute(name); 80 | } 81 | 82 | /* (non-Javadoc) 83 | * @see org.apache.http.protocol.HttpContext#setAttribute(java.lang.String, java.lang.Object) 84 | */ 85 | public void setAttribute(String name, Object val) { 86 | this.delegate.setAttribute(name, val); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/HttpResponseDecorator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | import java.util.Iterator; 25 | import java.util.Locale; 26 | 27 | import org.apache.http.Header; 28 | import org.apache.http.HeaderIterator; 29 | import org.apache.http.HttpEntity; 30 | import org.apache.http.HttpResponse; 31 | import org.apache.http.ProtocolVersion; 32 | import org.apache.http.StatusLine; 33 | import org.apache.http.params.HttpParams; 34 | import org.apache.http.protocol.ExecutionContext; 35 | import org.apache.http.protocol.HttpContext; 36 | 37 | /** 38 | * This class is a wrapper for {@link HttpResponse}, which allows for 39 | * simplified header access, as well as carrying the auto-parsed response data. 40 | * (see {@link HTTPBuilder#parseResponse(HttpResponse, Object)}). 41 | * 42 | * @see HeadersDecorator 43 | * @author Tom Nichols 44 | * @since 0.5.0 45 | */ 46 | public class HttpResponseDecorator implements HttpResponse { 47 | 48 | HeadersDecorator headers = null; 49 | HttpResponse responseBase; 50 | HttpContextDecorator context; 51 | Object responseData; 52 | 53 | public HttpResponseDecorator( HttpResponse base, Object parsedResponse ) { 54 | this( base, null, parsedResponse ); 55 | } 56 | 57 | public HttpResponseDecorator( HttpResponse base, HttpContextDecorator context, Object parsedResponse ) { 58 | this.responseBase = base; 59 | this.context = context; 60 | this.responseData = parsedResponse; 61 | } 62 | 63 | /** 64 | * Return a {@link HeadersDecorator}, which provides a more Groovy API for 65 | * accessing response headers. 66 | * @return the headers for this response 67 | */ 68 | public HeadersDecorator getHeaders() { 69 | if ( headers == null ) headers = new HeadersDecorator(); 70 | return headers; 71 | } 72 | 73 | /** 74 | * Quickly determine if the request resulted in an error code. 75 | * @return true if the response code is within the range of 76 | * {@link Status#SUCCESS} 77 | */ 78 | public boolean isSuccess() { 79 | return Status.find( getStatus() ) == Status.SUCCESS; 80 | } 81 | 82 | /** 83 | * Get the response status code. 84 | * @see StatusLine#getStatusCode() 85 | * @return the HTTP response code. 86 | */ 87 | public int getStatus() { 88 | return responseBase.getStatusLine().getStatusCode(); 89 | } 90 | 91 | /** 92 | * Get the content-type for this response. 93 | * @see ParserRegistry#getContentType(HttpResponse) 94 | * @return the content-type string, without any charset information. 95 | */ 96 | public String getContentType() { 97 | return ParserRegistry.getContentType( responseBase ); 98 | } 99 | 100 | /** 101 | * Return the parsed data from this response body. 102 | * @return the parsed response object, or null if the response 103 | * does not contain any data. 104 | */ 105 | public Object getData() { return this.responseData; } 106 | 107 | void setData( Object responseData ) { this.responseData = responseData; } 108 | 109 | /** 110 | * Get the execution context used during this request 111 | * @see ExecutionContext 112 | * @return the {@link HttpContext} 113 | */ 114 | public HttpContextDecorator getContext() { return this.context; } 115 | 116 | /** 117 | * This class is returned by {@link HttpResponseDecorator#getHeaders()}. 118 | * It provides three "Groovy" ways to access headers: 119 | *
120 | *
Bracket notation
resp.headers['Content-Type'] 121 | * returns the {@link Header} instance
122 | *
Property notation
resp.headers.'Content-Type' 123 | * returns the {@link Header#getValue() header value}
124 | *
Iterator methods
Iterates over each Header: 125 | *
resp.headers.each {
126 |      *   println "${it.name} : ${it.value}"
127 |      * }
128 | *
129 | * @author Tom Nichols 130 | * @since 0.5.0 131 | */ 132 | public final class HeadersDecorator implements Iterable
{ 133 | 134 | /** 135 | * Access the named header value, using bracket form. For example, 136 | * response.headers['Content-Encoding'] 137 | * @see HttpResponse#getFirstHeader(String) 138 | * @param name header name, e.g. Content-Type 139 | * @return the {@link Header}, or null if it does not exist 140 | * in this response 141 | */ 142 | public Header getAt( String name ) { 143 | return responseBase.getFirstHeader( name ); 144 | } 145 | 146 | /** 147 | * Allow property-style access to header values. This is the same as 148 | * {@link #getAt(String)}, except it simply returns the header's String 149 | * value, instead of the Header object. 150 | * 151 | * @param name header name, e.g. Content-Type 152 | * @return the {@link Header}, or null if it does not exist 153 | * in this response 154 | */ 155 | protected String propertyMissing( String name ) { 156 | Header h = this.getAt( name ); 157 | return h != null ? h.getValue() : null; 158 | } 159 | 160 | /** 161 | * Used to allow Groovy iteration methods over the response headers. 162 | * For example: 163 | *
response.headers.each {
164 |          *   println "${it.name} : ${it.value}"
165 |          * }
166 | */ 167 | @SuppressWarnings("unchecked") 168 | public Iterator iterator() { 169 | return responseBase.headerIterator(); 170 | } 171 | } 172 | 173 | 174 | public HttpEntity getEntity() { 175 | return responseBase.getEntity(); 176 | } 177 | 178 | public Locale getLocale() { 179 | return responseBase.getLocale(); 180 | } 181 | 182 | public StatusLine getStatusLine() { 183 | return responseBase.getStatusLine(); 184 | } 185 | 186 | public void setEntity( HttpEntity arg0 ) { 187 | responseBase.setEntity( arg0 ); 188 | } 189 | 190 | public void setLocale( Locale arg0 ) { 191 | responseBase.setLocale( arg0 ); 192 | } 193 | 194 | public void setReasonPhrase( String arg0 ) throws IllegalStateException { 195 | responseBase.setReasonPhrase( arg0 ); 196 | } 197 | 198 | public void setStatusCode( int arg0 ) throws IllegalStateException { 199 | responseBase.setStatusCode( arg0 ); 200 | } 201 | 202 | public void setStatusLine( StatusLine arg0 ) { 203 | responseBase.setStatusLine( arg0 ); 204 | } 205 | 206 | public void setStatusLine( ProtocolVersion arg0, int arg1 ) { 207 | responseBase.setStatusLine( arg0, arg1 ); 208 | } 209 | 210 | public void setStatusLine( ProtocolVersion arg0, int arg1, String arg2 ) { 211 | responseBase.setStatusLine( arg0, arg1, arg2 ); 212 | } 213 | 214 | public void addHeader( Header arg0 ) { 215 | responseBase.addHeader( arg0 ); 216 | } 217 | 218 | public void addHeader( String arg0, String arg1 ) { 219 | responseBase.addHeader( arg0, arg1 ); 220 | } 221 | 222 | public boolean containsHeader( String arg0 ) { 223 | return responseBase.containsHeader( arg0 ); 224 | } 225 | 226 | public Header[] getAllHeaders() { 227 | return responseBase.getAllHeaders(); 228 | } 229 | 230 | public Header getFirstHeader( String arg0 ) { 231 | return responseBase.getFirstHeader( arg0 ); 232 | } 233 | 234 | public Header[] getHeaders( String arg0 ) { 235 | return responseBase.getHeaders( arg0 ); 236 | } 237 | 238 | public Header getLastHeader( String arg0 ) { 239 | return responseBase.getLastHeader( arg0 ); 240 | } 241 | 242 | public HttpParams getParams() { 243 | return responseBase.getParams(); 244 | } 245 | 246 | public ProtocolVersion getProtocolVersion() { 247 | return responseBase.getProtocolVersion(); 248 | } 249 | 250 | public HeaderIterator headerIterator() { 251 | return responseBase.headerIterator(); 252 | } 253 | 254 | public HeaderIterator headerIterator( String arg0 ) { 255 | return responseBase.headerIterator( arg0 ); 256 | } 257 | 258 | public void removeHeader( Header arg0 ) { 259 | responseBase.removeHeader( arg0 ); 260 | } 261 | 262 | public void removeHeaders( String arg0 ) { 263 | responseBase.removeHeaders( arg0 ); 264 | } 265 | 266 | public void setHeader( Header arg0 ) { 267 | responseBase.setHeader( arg0 ); 268 | } 269 | 270 | public void setHeader( String arg0, String arg1 ) { 271 | responseBase.setHeader( arg0, arg1 ); 272 | } 273 | 274 | public void setHeaders( Header[] arg0 ) { 275 | responseBase.setHeaders( arg0 ); 276 | } 277 | 278 | public void setParams( HttpParams arg0 ) { 279 | responseBase.setParams( arg0 ); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/HttpResponseException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | /** 25 | * Wraps an error response in an exception for flow control purposes. That is, 26 | * you can still inspect response headers, but in a 27 | * catch( HttpResponseException ex ) { } block. 28 | * 29 | * @author Tom Nichols 30 | * @since 0.5 31 | */ 32 | public class HttpResponseException extends org.apache.http.client.HttpResponseException { 33 | 34 | private static final long serialVersionUID = -34809347677236L; 35 | 36 | HttpResponseDecorator response; 37 | 38 | public HttpResponseException( HttpResponseDecorator resp ) { 39 | super( resp.getStatusLine().getStatusCode(), 40 | resp.getStatusLine().getReasonPhrase() ); 41 | this.response = resp; 42 | } 43 | 44 | public HttpResponseDecorator getResponse() { 45 | return response; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/Method.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | import org.apache.http.HttpRequest; 25 | import org.apache.http.client.methods.HttpDelete; 26 | import org.apache.http.client.methods.HttpGet; 27 | import org.apache.http.client.methods.HttpHead; 28 | import org.apache.http.client.methods.HttpPost; 29 | import org.apache.http.client.methods.HttpPut; 30 | import org.apache.http.client.methods.HttpPatch; 31 | import org.apache.http.client.methods.HttpRequestBase; 32 | 33 | /** 34 | * Enumeration of valid HTTP methods that may be used in a 35 | * {@link HTTPBuilder#request(Method, groovy.lang.Closure) request} call. 36 | * @author Tom Nichols 37 | */ 38 | public enum Method { 39 | GET( HttpGet.class ), 40 | PUT( HttpPut.class ), 41 | POST( HttpPost.class ), 42 | DELETE( HttpDelete.class ), 43 | HEAD( HttpHead.class ), 44 | PATCH( HttpPatch.class ); 45 | 46 | private final Class requestType; 47 | 48 | /** 49 | * Get the HttpRequest class that represents this request type. 50 | * @return a non-abstract class that implements {@link HttpRequest} 51 | */ 52 | public Class getRequestType() { return this.requestType; } 53 | 54 | private Method( Class type ) { 55 | this.requestType = type; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/ResponseParseException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | /** 25 | * Thrown when a response body is parsed unsuccessfully. This most often 26 | * occurs when a server returns an error status code and sends a different 27 | * content-type body from what was expected. You can inspect the response 28 | * content-type by calling ex.response.contentType. 29 | * 30 | * @author Tom Nichols 31 | * @since 0.5.0 32 | */ 33 | public class ResponseParseException extends HttpResponseException { 34 | 35 | private static final long serialVersionUID = -1398234959324603287L; 36 | 37 | /* TODO this is a bit wonky because org.apache.http...HttpResponseException 38 | does not have a constructor to pass the 'cause'. But I want this to 39 | extend HttpResponseException so that one exception type can catch 40 | everything thrown from HttpBuilder. */ 41 | private Throwable cause; 42 | 43 | public ResponseParseException( HttpResponseDecorator response, Throwable cause ) { 44 | super( response ); 45 | this.cause = cause; 46 | } 47 | 48 | @Override public Throwable getCause() { return this.cause; } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/Status.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | /** 25 | * Mapping of HTTP response codes to a constant 'success' or 'failure' value. 26 | * @author Tom Nichols 27 | */ 28 | public enum Status { 29 | /** Any status code >= 100 and < 400 */ 30 | SUCCESS ( 100, 399 ), 31 | /** Any status code >= 400 and < 1000 */ 32 | FAILURE ( 400, 999 ); 33 | 34 | private final int min, max; 35 | 36 | @Override public String toString() { 37 | return super.toString().toLowerCase(); 38 | } 39 | 40 | /** 41 | * Returns true if the numeric code matches the represented status (either 42 | * success or failure). i.e. 43 | *
44 |      * assert Status.SUCCESS.matches(200);
45 |      * assert Status.FAILURE.matches(404);
46 |      * 
47 | * @param code numeric HTTP code 48 | * @return true if the numeric code represents this enums success or failure 49 | * condition 50 | */ 51 | public boolean matches( int code ) { 52 | return min <= code && code <= max; 53 | } 54 | 55 | /** 56 | * Find the Status value that matches the given status code. 57 | * @param code HTTP response code 58 | * @return a 'success' or 'failure' Status value 59 | * @throws IllegalArgumentException if the given code is not a valid HTTP 60 | * status code. 61 | */ 62 | public static Status find( int code ) { 63 | for ( Status s : Status.values() ) 64 | if ( s.matches( code ) ) return s; 65 | throw new IllegalArgumentException( "Unknown status: " + code ); 66 | } 67 | 68 | private Status( int min, int max ) { 69 | this.min = min; this.max = max; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/StringHashMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2011 Thomas Nichols. http://blog.thomnichols.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are encouraged (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http; 23 | 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | /** 28 | * Converts keys to strings, mainly to normalize the difference between 29 | * GString and String keys since a GString will not produce the same hashcode as 30 | * its equivalent string. 31 | * 32 | * Basically, any given key will always be coerced to a String, and any retrieved 33 | * key (either via {@link #keySet()} or {@link #entrySet()} will always be a 34 | * String. 35 | * @author Tom Nichols 36 | */ 37 | class StringHashMap extends HashMap { 38 | 39 | private static final long serialVersionUID = -92935672093270924L; 40 | 41 | public StringHashMap() { super(); } 42 | 43 | public StringHashMap( Map contents ) { 44 | super(); 45 | this.putAll( contents ); 46 | } 47 | 48 | @Override 49 | public boolean containsKey( Object key ) { 50 | if ( key == null ) return false; 51 | return super.containsKey( key.toString() ); 52 | } 53 | 54 | @Override 55 | public V get( Object key ) { 56 | if ( key == null ) return null; 57 | return super.get( key.toString() ); 58 | } 59 | 60 | public V put(Object key, V value) { 61 | return key != null ? super.put( key.toString(), value ) : value; 62 | } 63 | 64 | @Override 65 | public void putAll( Map m ) { 66 | for ( Object key : m.keySet() ) this.put( key, m.get( key ) ); 67 | } 68 | 69 | @Override 70 | public V remove( Object key ) { 71 | if ( key == null ) return null; 72 | return super.remove( key.toString() ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

7 | HTTPBuilder provides a simple Groovy API for HTTP and REST client operations. 8 | It supports multiple callbacks based on response status code, and a mechanism 9 | to automatically parse and encode many common content-types such as XML, JSON, 10 | HTML, and plain text. HTTPBuilder also supports easy configuration for common 11 | authentication mechanisms, and common content-encodings as well. 12 |

13 | 14 |

Package Specification

15 | 16 |

17 | The primary class is {@link groovyx.net.http.HTTPBuilder HTTPBuilder}, which 18 | provides the most features and functionality. 19 | {@link groovyx.net.http.RESTClient RESTClient}, 20 | {@link groovyx.net.http.AsyncHTTPBuilder AsyncHTTPBuilder} and 21 | {@link groovyx.net.http.HttpURLClient HttpURLClient} are variants provided 22 | for more specialized use cases. 23 |

24 | 25 |

{@link groovyx.net.http.URIBuilder URIBuilder} is used by HTTPBuilder classes 26 | for URL manipulation, but it may also be useful on its own. The remaining 27 | classes provide supporting functions for HTTPBuilder and are probably only 28 | useful for customizing or extending HTTPBuilder functionality.

29 | 30 | 31 |

Related Documentation

32 | 33 | For overviews, tutorials, examples, guides, and tool documentation, please see: 34 | 38 | 39 | For content-type parser and encoder support, see: 40 | 44 | 45 | 46 | 47 | 48 | @author Tom Nichols 49 | @see groovyx.net.http.HTTPBuilder 50 | @see groovyx.net.http.RESTClient 51 | @see groovyx.net.http.URIBuilder 52 | @see groovyx.net.http.AsyncHTTPBuilder 53 | @see groovyx.net.http.HttpURLClient 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/thirdparty/GAEClientConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | ESXX - The friendly ECMAscript/XML Application Server 3 | Copyright (C) 2007-2010 Martin Blom 4 | 5 | This program is free software: you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public License 7 | as published by the Free Software Foundation, either version 3 8 | of the License, or (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | 18 | 19 | PLEASE NOTE THAT THIS FILE'S LICENSE IS DIFFERENT FROM THE REST OF ESXX! 20 | */ 21 | 22 | package groovyx.net.http.thirdparty; 23 | 24 | import java.io.*; 25 | import java.net.*; 26 | import java.util.concurrent.TimeUnit; 27 | import org.apache.http.*; 28 | import org.apache.http.conn.*; 29 | import org.apache.http.conn.routing.HttpRoute; 30 | import org.apache.http.entity.ByteArrayEntity; 31 | import org.apache.http.message.BasicHttpResponse; 32 | import org.apache.http.params.*; 33 | import org.apache.http.protocol.*; 34 | 35 | import com.google.appengine.api.urlfetch.*; 36 | 37 | class GAEClientConnection 38 | implements ManagedClientConnection { 39 | 40 | public GAEClientConnection(ClientConnectionManager cm, HttpRoute route, Object state) { 41 | this.connManager = cm; 42 | this.route = route; 43 | this.state = state; 44 | this.closed = true; 45 | } 46 | 47 | // From interface ManagedClientConnection 48 | 49 | public boolean isSecure() { 50 | return route.isSecure(); 51 | } 52 | 53 | public HttpRoute getRoute() { 54 | return route; 55 | } 56 | 57 | public javax.net.ssl.SSLSession getSSLSession() { 58 | return null; 59 | } 60 | 61 | public void open(HttpRoute route, HttpContext context, HttpParams params) 62 | throws IOException { 63 | close(); 64 | this.route = route; 65 | // System.err.println(">>>>"); 66 | } 67 | 68 | public void tunnelTarget(boolean secure, HttpParams params) 69 | throws IOException { 70 | throw new IOException("tunnelTarget() not supported"); 71 | } 72 | 73 | public void tunnelProxy(HttpHost next, boolean secure, HttpParams params) 74 | throws IOException { 75 | throw new IOException("tunnelProxy() not supported"); 76 | } 77 | 78 | public void layerProtocol(HttpContext context, HttpParams params) 79 | throws IOException { 80 | throw new IOException("layerProtocol() not supported"); 81 | } 82 | 83 | public void markReusable() { 84 | reusable = true; 85 | } 86 | 87 | public void unmarkReusable() { 88 | reusable = false; 89 | } 90 | 91 | public boolean isMarkedReusable() { 92 | return reusable; 93 | } 94 | 95 | public void setState(Object state) { 96 | this.state = state; 97 | } 98 | 99 | public Object getState() { 100 | return state; 101 | } 102 | 103 | public void setIdleDuration(long duration, TimeUnit unit) { 104 | // Do nothing 105 | } 106 | 107 | 108 | // From interface HttpClientConnection 109 | 110 | public boolean isResponseAvailable(int timeout) 111 | throws IOException { 112 | return response != null; 113 | } 114 | 115 | 116 | public void sendRequestHeader(HttpRequest request) 117 | throws HttpException, IOException { 118 | try { 119 | HttpHost host = route.getTargetHost(); 120 | 121 | URI uri = new URI(host.getSchemeName() 122 | + "://" 123 | + host.getHostName() 124 | + ((host.getPort() == -1) ? "" : (":" + host.getPort())) 125 | + request.getRequestLine().getUri()); 126 | 127 | this.request = new HTTPRequest(uri.toURL(), 128 | HTTPMethod.valueOf(request.getRequestLine().getMethod()), 129 | FetchOptions.Builder.disallowTruncate().doNotFollowRedirects()); 130 | } 131 | catch (URISyntaxException ex) { 132 | throw new IOException("Malformed request URI: " + ex.getMessage(), ex); 133 | } 134 | catch (IllegalArgumentException ex) { 135 | throw new IOException("Unsupported HTTP method: " + ex.getMessage(), ex); 136 | } 137 | 138 | // System.err.println("SEND: " + this.request.getMethod() + " " + this.request.getURL()); 139 | 140 | for (Header h : request.getAllHeaders()) { 141 | // System.err.println("SEND: " + h.getName() + ": " + h.getValue()); 142 | this.request.addHeader(new HTTPHeader(h.getName(), h.getValue())); 143 | } 144 | } 145 | 146 | 147 | public void sendRequestEntity(HttpEntityEnclosingRequest request) 148 | throws HttpException, IOException { 149 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 150 | if (request.getEntity() != null) { 151 | request.getEntity().writeTo(baos); 152 | } 153 | this.request.setPayload(baos.toByteArray()); 154 | } 155 | 156 | 157 | public HttpResponse receiveResponseHeader() 158 | throws HttpException, IOException { 159 | if (this.response == null) { 160 | flush(); 161 | } 162 | 163 | HttpResponse response = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1), 164 | this.response.getResponseCode(), 165 | null); 166 | // System.err.println("RECV: " + response.getStatusLine()); 167 | 168 | for (HTTPHeader h : this.response.getHeaders()) { 169 | // System.err.println("RECV: " + h.getName() + ": " + h.getValue()); 170 | response.addHeader(h.getName(), h.getValue()); 171 | } 172 | 173 | return response; 174 | } 175 | 176 | 177 | public void receiveResponseEntity(HttpResponse response) 178 | throws HttpException, IOException { 179 | if (this.response == null) { 180 | throw new IOException("receiveResponseEntity() called on closed connection"); 181 | } 182 | 183 | ByteArrayEntity bae = new ByteArrayEntity(this.response.getContent()); 184 | bae.setContentType(response.getFirstHeader("Content-Type")); 185 | response.setEntity(bae); 186 | 187 | response = null; 188 | } 189 | 190 | public void flush() 191 | throws IOException { 192 | if (request != null) { 193 | try { 194 | // System.err.println("----"); 195 | response = urlFS.fetch(request); 196 | request = null; 197 | }catch (IOException ex) { 198 | ex.printStackTrace(); 199 | throw ex; 200 | } 201 | } 202 | else { 203 | response = null; 204 | } 205 | } 206 | 207 | 208 | // From interface HttpConnection 209 | 210 | public void close() 211 | throws IOException { 212 | request = null; 213 | response = null; 214 | closed = true; 215 | // System.err.println("<<<<"); 216 | } 217 | 218 | public boolean isOpen() { 219 | return request != null || response != null; 220 | } 221 | 222 | public boolean isStale() { 223 | return !isOpen() && !closed; 224 | } 225 | 226 | public void setSocketTimeout(int timeout) { 227 | } 228 | 229 | public int getSocketTimeout() { 230 | return -1; 231 | } 232 | 233 | public void shutdown() 234 | throws IOException { 235 | close(); 236 | } 237 | 238 | public HttpConnectionMetrics getMetrics() { 239 | return null; 240 | } 241 | 242 | 243 | // From interface HttpInetConnection 244 | 245 | public InetAddress getLocalAddress() { 246 | return null; 247 | } 248 | 249 | public int getLocalPort() { 250 | return 0; 251 | } 252 | 253 | public InetAddress getRemoteAddress() { 254 | return null; 255 | } 256 | 257 | public int getRemotePort() { 258 | HttpHost host = route.getTargetHost(); 259 | return connManager.getSchemeRegistry().getScheme(host).resolvePort(host.getPort()); 260 | } 261 | 262 | 263 | // From interface ConnectionReleaseTrigger 264 | 265 | public void releaseConnection() 266 | throws IOException { 267 | connManager.releaseConnection(this, Long.MAX_VALUE, TimeUnit.MILLISECONDS); 268 | } 269 | 270 | public void abortConnection() 271 | throws IOException { 272 | unmarkReusable(); 273 | shutdown(); 274 | } 275 | 276 | private ClientConnectionManager connManager; 277 | private HttpRoute route; 278 | private Object state; 279 | private boolean reusable; 280 | 281 | private HTTPRequest request; 282 | private HTTPResponse response; 283 | private boolean closed; 284 | 285 | private static URLFetchService urlFS = URLFetchServiceFactory.getURLFetchService(); 286 | } 287 | -------------------------------------------------------------------------------- /src/main/java/groovyx/net/http/thirdparty/GAEConnectionManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | ESXX - The friendly ECMAscript/XML Application Server 3 | Copyright (C) 2007-2010 Martin Blom 4 | 5 | This program is free software: you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public License 7 | as published by the Free Software Foundation, either version 3 8 | of the License, or (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | 18 | 19 | PLEASE NOTE THAT THIS FILE'S LICENSE IS DIFFERENT FROM THE REST OF ESXX! 20 | */ 21 | 22 | package groovyx.net.http.thirdparty; 23 | 24 | import java.net.*; 25 | import java.util.concurrent.TimeUnit; 26 | import org.apache.http.conn.*; 27 | import org.apache.http.params.*; 28 | import org.apache.http.conn.routing.HttpRoute; 29 | import org.apache.http.conn.scheme.*; 30 | 31 | public class GAEConnectionManager 32 | implements ClientConnectionManager { 33 | 34 | public GAEConnectionManager() { 35 | SocketFactory no_socket_factory = new SocketFactory() { 36 | public Socket connectSocket(Socket sock, String host, int port, 37 | InetAddress localAddress, int localPort, 38 | HttpParams params) { 39 | return null; 40 | } 41 | 42 | public Socket createSocket() { 43 | return null; 44 | } 45 | 46 | public boolean isSecure(Socket s) { 47 | return false; 48 | } 49 | }; 50 | 51 | schemeRegistry = new SchemeRegistry(); 52 | schemeRegistry.register(new Scheme("http", no_socket_factory, 80)); 53 | schemeRegistry.register(new Scheme("https", no_socket_factory, 443)); 54 | } 55 | 56 | 57 | public SchemeRegistry getSchemeRegistry() { 58 | return schemeRegistry; 59 | } 60 | 61 | public ClientConnectionRequest requestConnection(final HttpRoute route, 62 | final Object state) { 63 | return new ClientConnectionRequest() { 64 | public void abortRequest() { 65 | // Nothing to do 66 | } 67 | 68 | public ManagedClientConnection getConnection(long timeout, TimeUnit tunit) { 69 | return GAEConnectionManager.this.getConnection(route, state); 70 | } 71 | }; 72 | } 73 | 74 | public void releaseConnection(ManagedClientConnection conn, 75 | long validDuration, TimeUnit timeUnit) { 76 | } 77 | 78 | public void closeIdleConnections(long idletime, TimeUnit tunit) { 79 | } 80 | 81 | public void closeExpiredConnections() { 82 | } 83 | 84 | public void shutdown() { 85 | } 86 | 87 | private ManagedClientConnection getConnection(HttpRoute route, Object state) { 88 | return new GAEClientConnection(this, route, state); 89 | } 90 | 91 | private SchemeRegistry schemeRegistry; 92 | } 93 | -------------------------------------------------------------------------------- /src/main/resources/catalog/HTMLspecial.ent: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 13 | 14 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 37 | 39 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | 54 | 55 | 56 | 57 | 58 | 60 | 62 | 63 | 65 | 67 | 68 | 69 | 70 | 71 | 73 | 74 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/main/resources/catalog/frameset.dtd: -------------------------------------------------------------------------------- 1 | 20 | 25 | 26 | 27 | ... 28 | 29 | 30 | ... 31 | 32 | 33 | --> 34 | 35 | 36 | 37 | %HTML4.dtd; 38 | -------------------------------------------------------------------------------- /src/main/resources/catalog/html.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 14 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/resources/catalog/xhtml-special.ent: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 38 | 40 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | 61 | 63 | 64 | 66 | 68 | 69 | 70 | 71 | 72 | 74 | 75 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/main/script/release_tweet.groovy: -------------------------------------------------------------------------------- 1 | // Send a Twitter update when a release is made. Cool! 2 | 3 | import groovyx.net.http.HTTPBuilder 4 | 5 | import static groovyx.net.http.Method.* 6 | import static groovyx.net.http.ContentType.* 7 | 8 | def http = new HTTPBuilder('http://twitter.com/statuses/') 9 | 10 | http.auth.oauth pom.properties.'twitter.oauth.consumerKey', 11 | pom.properties.'twitter.oauth.consumerSecret', 12 | pom.properties.'twitter.oauth.accessToken', 13 | pom.properties.'twitter.oauth.secretToken' 14 | 15 | def msg = "v${pom.version} has been released! (${new Date()}) http://goo.gl/VzuT #groovy" 16 | 17 | println "Tweeting release for v${pom.version}..." 18 | 19 | http.request( POST, XML ) { req -> 20 | uri.path = 'update.xml' 21 | send URLENC, [status:msg, source:'httpbuilder'] 22 | 23 | response.success = { resp, xml -> 24 | println "Tweet response status: ${resp.statusLine}" 25 | assert resp.statusLine.statusCode == 200 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/script/twitter_restbuilder.groovy: -------------------------------------------------------------------------------- 1 | /* Example script that demonstrates use of the Twitter API. 2 | * 3 | */ 4 | 5 | import groovyx.net.http.RESTClient 6 | import groovy.util.slurpersupport.GPathResult 7 | import static groovyx.net.http.ContentType.URLENC 8 | 9 | twitter = new RESTClient( 'https://twitter.com/statuses/' ) 10 | twitter.auth.oauth System.getProperty('twitter.oauth.consumerKey'), 11 | System.getProperty('twitter.oauth.consumerSecret'), 12 | System.getProperty('twitter.oauth.accessToken'), 13 | System.getProperty('twitter.oauth.secretToken') 14 | 15 | // Test a URL using the HEAD method: 16 | 17 | try { // expect an exception from a 404 response: 18 | twitter.head path : 'public_timeline' 19 | assert false, 'Expected exception' 20 | } 21 | /* The exception is used for flow control but can be used 22 | to read the response: */ 23 | catch( ex ) { assert ex.response.status == 404 } 24 | 25 | assert twitter.head( path : 'public_timeline.json' ).status == 200 26 | 27 | 28 | 29 | // GET our friends' timeline: 30 | 31 | def resp = twitter.get( path : 'friends_timeline.json' ) 32 | assert resp.status == 200 33 | assert resp.contentType == JSON.toString() 34 | assert ( resp.data instanceof net.sf.json.JSON ) 35 | assert resp.data.status.size() > 0 36 | 37 | 38 | // POST a status update to twitter! 39 | 40 | def msg = "I'm using HTTPBuilder's RESTClient on ${new Date()}" 41 | 42 | resp = twitter.post( path : 'update.xml', 43 | body : [ status:msg, source:'httpbuilder' ], 44 | requestContentType : URLENC ) 45 | 46 | assert resp.status == 200 47 | assert ( resp.data instanceof GPathResult ) // parsed using XmlSlurper 48 | assert resp.data.text == msg 49 | assert resp.data.user.screen_name == userName 50 | def postID = resp.data.id 51 | 52 | 53 | // Now let's delete that post: 54 | 55 | resp = twitter.delete( path : "destroy/${postID}.json" ) 56 | assert resp.status == 200 57 | assert resp.data.id == postID 58 | println "Test tweet ID ${resp.data.id} was deleted." 59 | -------------------------------------------------------------------------------- /src/site/apt/changes.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | Change Log 3 | ------ 4 | ------ 5 | ------ 6 | 7 | 8 | Change Log 9 | 10 | * v0.7.1 - 25 Feb 2014 11 | 12 | * Allow override of RESTClient.doRequest() (from Diego Fernandez) 13 | 14 | * New method HTTPBuilder.ignoreSSLIssues() (from Adam Hurwitz) 15 | 16 | * v0.7 - 4 Feb 2014 17 | 18 | * Groovy dependency is now 'provided' scope to address conflicts with different Groovy versions. 19 | 20 | * PATCH method now supported (from David Prieto). 21 | 22 | * Order no longer matters when setting the body in a request closure. 23 | 24 | * Support for NTLM authentication (from Damien Papworth). 25 | 26 | * Validate requestContentType is set (from Tomasz Kalkosinski). 27 | 28 | * Allow any HttpClient implementation (from Dana P'Simer and Jean-Alain Geay). 29 | 30 | * Documentation examples previously dependent on the v1.0 Twitter API have been updated. 31 | 32 | * v0.6.0 - 16 Oct 2012 33 | 34 | * Now requires Groovy 1.8 35 | 36 | * Upgrade HttpClient dependency to 4.2.1 37 | 38 | * Upgrade Nekohtml dependency to 1.9.16 39 | 40 | * Changed to use Groovy's native JsonSlurper to consume JSON, rather than jsonlib 41 | 42 | * v0.5.2 - 26 Dec 2011 43 | 44 | * Support for escaped URI query parameters 45 | 46 | * Fixes for AsyncHTTPBuilder 47 | 48 | * AppEngine connection manager 49 | 50 | * Ability to customize HttpClient construction e.g. with a connection manager 51 | 52 | * v0.5.1 - 30 Sept 2010 53 | 54 | * OAuth support via Signpost 55 | 56 | * Access to the request context used in the response 57 | 58 | * Updated HttpClient dependency to 4.0.3 59 | 60 | * Fixed cookie expiry date parsing 61 | 62 | * Fixed parsing URL-encoded response 63 | 64 | * v0.5.0 - 03 May 2010 65 | 66 | * RESTClient for more REST-ful request API. 67 | 68 | * HttpURLClient for a similar API that is compatible with Google App Engine. 69 | 70 | * Added resolver catalog for (X)HTML DTDs 71 | 72 | * Improved header access API 73 | 74 | * Moved project to GMOD Maven repo. New groupId is <<>> 75 | 76 | * Changed "url" property names to "uri" (breaking change!) 77 | 78 | * HTTPBuilder's "uri" property returns a URIBuilder, to make default 79 | URL manipulation easier. 80 | 81 | * Renamed SendDelegate to RequestConfigDelegate (possibly breaking change) 82 | 83 | * Fixed bug when assigning default URI via named argument 84 | 85 | * Fixed default response handlers for AsyncHTTPBuilder 86 | 87 | * Fixed illegal character encoding in <<>> 88 | 89 | * Fixed default response handlers in AsyncHTTPBuilder 90 | 91 | * Changed request configuration closure resolution strategy to DELEGATE_FIRST 92 | 93 | * v0.4.1 - 04 Mar 2009 94 | 95 | * Fixed double-encoding of URI parameters 96 | 97 | * Added proxy support 98 | 99 | * Updated to HttpClient 4.0-beta2 100 | 101 | * v0.4.0 - 14 Jan 2009 102 | 103 | * Updated default success handler to buffer and return parsed data outside of 104 | request/response context 105 | 106 | * v0.3.1 - 5 Jan 2009 107 | 108 | * Corrected <<>> value 109 | 110 | * v0.3.0 - 24 Dec 2008 111 | 112 | * Added AsyncHTTPBuilder 113 | 114 | * Fixed bug in ParserRegistry.getCharset 115 | 116 | * Changed parser resolution to always use given charset unless 'ANY' is used 117 | 118 | * Added xref report to site documentation 119 | 120 | * Improved header handling 121 | 122 | * Fix for NPE when response has no entity (i.e. HEAD) 123 | 124 | * v0.2.2 - 18 Dec 2008 125 | 126 | * Fixed bug when 'path' named parameter is a GString 127 | 128 | * Removed Java 1.6 dependencies 129 | 130 | * Documentation 131 | 132 | * v0.2.1 - 16 Dec 2008 133 | 134 | * Changed package name & added to Groovy-contrib 135 | 136 | * Added support for GZIP and deflate response content-encoding 137 | 138 | * Support for default request headers & changing headers via map access 139 | 140 | * Fixed bug from json-lib 141 | 142 | * Built-in HTML parsing support via Neko 143 | 144 | * Support for multiple equivalent content-types 145 | 146 | * Differing request and response content-types 147 | 148 | * Only attempt to parse response if the receiving response handler accepts 149 | two parameters 150 | 151 | * Added license to source code 152 | 153 | * v0.1 - 8 Dec 2008 154 | 155 | * Initial release 156 | 157 | -------------------------------------------------------------------------------- /src/site/apt/contrib.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | Contributions 3 | ------ 4 | ------ 5 | ------ 6 | 7 | Contributions and Extensions 8 | 9 | Since HTTPBuilder is meant to be a general-purpose (and extensible) tool, it's 10 | not surprising that other users will create adaptations for specific tasks. 11 | What are other people doing with HTTPBuilder? Share your extensions! 12 | 13 | <<{{{http://pastebin.com/ZGY25cq4}OAuth for HTTPBuilder}}>> by Erem Boto \ 14 | Simple class to support OAuth using the {{{http://github.com/fernandezpablo85/scribe}Scribe project}}. 15 | {{{http://pastebin.com/ywagxPdP}Spock test class}}. 16 | 17 | <<{{{http://code.google.com/p/css-selector-httpbuilder/}CSS Selector}}>> \ 18 | Naviagate a parsed HTML tree using CSS selectors rather than GPath. 19 | -------------------------------------------------------------------------------- /src/site/apt/doc/async.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | Asynchronous HTTPBuilder 3 | ------ 4 | ------ 5 | ------ 6 | 7 | 8 | 9 | AsyncHTTPBuilder 10 | 11 | AsyncHTTPBuilder is very similar to HTTPBuilder, except that all requests are 12 | delegated to a thread pool and executed asynchronously. Requests return a 13 | {{{http://java.sun.com/j2se/1.5.0/docs/api/index.html?java/util/concurrent/Future.html}java.util.concurrent.Future}} 14 | instance, which can be used to access the response data once it has completed. 15 | 16 | %{code-snippet|id=async1|brush=groovy|file=src/site/examples.txt} 17 | 18 | In practice, it is very similar to the Ajax.Request class in Prototype.js. 19 | 20 | This class also demonstrates the ease of which HTTPBuilder can be extended. 21 | The <<>> method was simply overridden in order to execute requests 22 | from a <<>>. You can see the full source code 23 | {{{../xref/groovyx/net/http/AsyncHTTPBuilder.html}here}}. 24 | -------------------------------------------------------------------------------- /src/site/apt/doc/auth.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | Authentication 3 | ------ 4 | ------ 5 | ------ 6 | 7 | 8 | Authentication 9 | 10 | HTTPBuilder supports a number of authentication mechanisms natively, to make 11 | working with protected sites as simple as possible. 12 | 13 | *Basic Authentication 14 | 15 | Basic auth isn't terribly complicated in Apache HttpClient, but it's dead-simple 16 | in HTTPBuilder: 17 | 18 | %{code-snippet|id=auth3|brush=groovy|file=src/site/examples.txt} 19 | 20 | When a request is made, the server will respond with a 401 status and a 21 | <<>> header, indicating that authorization is required. 22 | HTTPBuilder will then re-request with the proper <<>> header 23 | containing your credentials. This is all transparent to the developer, as it 24 | simply appears that the first request completed successfully. 25 | 26 | See the {{{http://hc.apache.org/httpcomponents-client-ga/tutorial/html/authentication.html}HTTPClient authentication doc}} 27 | for more details. 28 | 29 | *OAuth Support 30 | 31 | {{{http://oauth.net}OAuth}} is supported in HTTPBuilder since version 0.5.1. 32 | Support is provided by the {{{http://code.google.com/p/oauth-signpost/}Signpost framework}} 33 | in HTTPBuilder, RESTClient and HttpURLClient. 34 | 35 | ** Obtaining an Access Token Pair 36 | 37 | The Signpost documentation contains {{{http://code.google.com/p/oauth-signpost/wiki/TwitterAndSignpost}an example}} 38 | of how to obtain access tokens out-of-band. It's not completely 39 | straightforward, but it can be made significantly easier by using an 40 | interactive Groovy shell session. This allows execution to pause while you 41 | switch to your browser to access the validation URL. Below is an example: 42 | 43 | %{code-snippet|id=auth1|brush=groovy|file=src/site/examples.txt} 44 | 45 | Your app will now be authorized to make requests on behalf of the user that was 46 | logged in when you accessed the given URL in your browser. Now that you have 47 | your consumer key, consumer secret, access token an access key, give all four 48 | values to HTTPBuilder like so: 49 | 50 | %{code-snippet|id=auth2|brush=groovy|file=src/site/examples.txt} 51 | 52 | **A final note about OAuth 53 | 54 | Unlike basic auth and other schemes supported natively by HttpClient, OAuth 55 | does wait for a <<>> challenge from the server before signing 56 | a request, and OAuth cannot be configured for multiple domains in a single 57 | HTTPBuilder client. The means that once you call <<>>, 58 | request signing is effectively 'turned on' for every request you make with that 59 | HTTPBuilder instance. If you want to signing requests, call 60 | <<>>. 61 | 62 | *Other Auth Mechanisms 63 | 64 | HTTPBuilder and RESTClient also support 'Digest' and certificate authentication 65 | out-of-the-box, which simply exposes the underlying features present in Apache 66 | HttpClient. See the {{{../apidocs/groovyx/net/http/AuthConfig.html}AuthConfig javadoc}} 67 | for a list of all authentication schemes supported. Currently HttpURLClient 68 | only supports 'Basic' and 'OAuth' authentication. 69 | -------------------------------------------------------------------------------- /src/site/apt/doc/contentTypes.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | Content Types 3 | ------ 4 | ------ 5 | ------ 6 | 7 | 8 | Content-Type Response Parsing 9 | 10 | One of HTTPBuilder's strengths is its in-built ability to parse many content-types. 11 | If a response is <<>> it is automatically parsed by <<>>. 12 | An <<>> response will be parsed into a POGO object via Json-Lib, 13 | and a <<>> response will come back as a <<>>. This is done 14 | by comparing the response's <<>> header to entries in the current 15 | <<<{{{../apidocs/groovyx/net/http/ParserRegistry.html}ParserRegistry}}>>> instance. 16 | 17 | You can see the default content-types handled by HTTPBuilder in the 18 | {{{../apidocs/groovyx/net/http/ParserRegistry.html#buildDefaultParserMap()}ParserRegistry class}}}. 19 | 20 | If you want to override the automatic parsing (say, to get an XML response as a plain 21 | string) you can tell HTTPBuilder how it should try to parse the text. (This 22 | is also handy if a server is being dumb and serving HTML data as plain text, for instance.) 23 | 24 | The <<>> parameter tells HTTPBuilder how it should parse the response. 25 | 26 | %{code-snippet|id=contenttype3|brush=groovy|file=src/site/examples.txt} 27 | 28 | The <<>> parameter also instructs HTTPBuilder to add an <<>> header 29 | to the request for the specified content-type. Assuming we still want to JSON data, 30 | but not parse it, the <<>> line above overrides the automatic 31 | <<>> header that would otherwise be added by the <<>> 32 | argument. 33 | 34 | Now assume we want to handle XML as plain text. (maybe you're passing it on to 35 | a SAX parser or something similar.) We can re-use the TEXT content-type handler by assigning 36 | it to the XML content-type in the ParserRegistry: 37 | 38 | %{code-snippet|id=contenttype4|brush=groovy|file=src/site/examples.txt} 39 | 40 | Now, any response that comes in with a <<>> header will be handled 41 | using the plain-text parser -- meaning it's simply returned as a reader. 42 | 43 | 44 | Support for new Content Types 45 | 46 | To add parsing for new content types, simply add a new entry to the builder's 47 | <<<{{{../apidocs/groovyx/net/http/ParserRegistry.html}ParserRegistry}}>>>. 48 | For example, to parse comma-separated-values using 49 | {{{http://opencsv.sourceforge.net/}OpenCSV}}: 50 | 51 | %{code-snippet|id=contenttype1|brush=groovy|file=src/site/examples.txt} 52 | 53 | A <<>> instance will then be passed as the second argument to the 54 | response handler: 55 | 56 | %{code-snippet|id=contenttype2|brush=groovy|file=src/site/examples.txt} 57 | 58 | You can refer to {{{http://www.iana.org/assignments/media-types/}IANA}} for a 59 | complete list of registered content-type names, but the most common are 60 | already handled in the {{{../apidocs/groovyx/net/http/ContentType.html}ContentType}} enumeration. 61 | -------------------------------------------------------------------------------- /src/site/apt/doc/get.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | GET Examples 3 | ------ 4 | ------ 5 | ------ 6 | 7 | Simplified GET Request 8 | 9 | HTTPBuilder has convenience methods for <<>> and <<<{{{post}POST}}>>> 10 | methods. Here is a simple HTTP <<>> which parses the response as a DOM 11 | object: 12 | 13 | %{code-snippet|id=get1|brush=groovy|file=src/site/examples.txt} 14 | 15 | 16 | In the above example, we are making a request, and automatically parsing the 17 | HTML response based on the response's content-type header. The HTML stream is 18 | normalized (thanks to {{{http://nekohtml.sourceforge.net/}Neko,}}) and then 19 | parsed by an 20 | {{{http://groovy.codehaus.org/Reading+XML+using+Groovy%27s+XmlSlurper}XmlSlurper}} 21 | for easy DOM traversal. 22 | 23 | We are also taking advantage of HTTPBuilder's 24 | {{{../apidocs/groovyx/net/http/HTTPBuilder.html#defaultSuccessHandler(groovyx.net.http.HttpResponseDecorator,%20java.lang.Object)}default 25 | success handler}} since we're not supplying a 'success' response handler 26 | closure. The default handler defined by HTTPBuilder (which can be overridden) 27 | simply returns the parsed response data. 28 | 29 | Next is another <<>> request, with custom response-handling logic that 30 | prints the response to <<>>: 31 | 32 | %{code-snippet|id=get2|brush=groovy|file=src/site/examples.txt} 33 | 34 | 35 | In this version, the <<<{{{../apidocs/groovyx/net/http/HTTPBuilder.html#get%28java.util.Map%2C%20groovy.lang.Closure%29}get()}}>>> 36 | method also accepts a closure, which is interpreted as the 'success' response 37 | handler. A failure response (i.e. status code of 400 or greater) is still handled by the builder's 38 | {{{../apidocs/groovyx/net/http/HTTPBuilder.html#defaultFailureHandler(groovyx.net.http.HttpResponseDecorator)} 39 | default failure handler}}. 40 | 41 | Additionally, we are telling HTTPBuilder to parse the response as 42 | <<>> - which is a built-in type, handled by the default 43 | <<<{{{../apidocs/groovyx/net/http/ParserRegistry.html#parseText(org.apache.http.HttpResponse)} 44 | ParserRegistry}}>>> to automatically create a <<>> from the response data. 45 | 46 | 47 | <<>> using the <<>> method 48 | 49 | HTTPBuilder supports a generic <<>> method that can be used to configure 50 | the request in a fine-grained manner. 51 | 52 | %{code-snippet|id=get3|brush=groovy|file=src/site/examples.txt} 53 | 54 | 55 | In the above example, a 'request configuration closure' is defined as the final 56 | argument to the request method. This closure allows fine-grained configuration 57 | of the request and response handling behavior. Within the closure, there are a 58 | number of properties available via the {{{../apidocs/groovyx/net/http/HTTPBuilder.RequestConfigDelegate.html}closure's delegate}}. 59 | <<>> is an instance of 60 | <<<{{{http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/index.html?org/apache/http/HttpRequest.html}HttpRequest}}>>> 61 | to directly access the Apache HttpClient API. 62 | -------------------------------------------------------------------------------- /src/site/apt/doc/handlers.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | Response Handlers 3 | ------ 4 | ------ 5 | ------ 6 | 7 | 8 | A 'Response Handler' is the closure that is executed to parse the HTTP 9 | response that is returned from the server. Multiple handlers are usually 10 | defined, and correct handler is chosen based on the HTTP status 11 | code in the response. 12 | 13 | 14 | Success vs Failure 15 | 16 | Generally, status codes may be grouped into two categories - 'success' (any 17 | status less than 400) or 'failure' (400 or greater). Within the request 18 | configuration closure, handlers may be defined through the <<>> 19 | property, which returns a map of response closures. This map may then be used 20 | to add response handlers for this request: 21 | 22 | %{code-snippet|id=handler1|brush=groovy|file=src/site/examples.txt} 23 | 24 | These status handlers may be combined with more specific handlers that only 25 | handle a specific response code: 26 | 27 | %{code-snippet|id=handler2|brush=groovy|file=src/site/examples.txt} 28 | 29 | In all cases, a handler will first be retrieved by the exact status code, and 30 | if none is found, it will fall back to a generic 'success' or 'failure' 31 | handler. 32 | 33 | *A Note on 'Intermediate' Status Codes 34 | 35 | In cases where a response sends a redirect status code, this is 36 | handled internally by Apache HttpClient, which by default will simply follow 37 | the redirect by re-sending the request to the new URL. You do not need to 38 | do anything special in order to follow 302 responses. 39 | 40 | Similarly, a 401 status code can be handled transparently by HttpClient when 41 | authorization has been configured. In most cases, you (the user) are not 42 | interested in these 'intermediate' responses, so they are handled internally 43 | by the framework. If you to handle these responses directly, it can 44 | be configured through the underlying HttpClient instance. 45 | 46 | Similarly, HttpURLClient has a 47 | <<<{{{../apidocs/groovyx/net/http/HttpURLClient.html#setFollowRedirects(boolean)}followRedirects}}>>> 48 | property to configure redirect behavior. 49 | 50 | Default Handlers 51 | 52 | The default handlers are good for cases where the user is not interested in 53 | dealing with streaming responses. In this case, the response data is simply 54 | parsed (or buffered in the case of a binary or text response) and returned 55 | from the request method: 56 | 57 | %{code-snippet|id=handler3|brush=groovy|file=src/site/examples.txt} 58 | 59 | The default failure handler will throw an exception. 60 | 61 | This behavior can also be customized by setting your own default handlers on 62 | the HTTPBuilder instance like so: 63 | 64 | %{code-snippet|id=handler4|brush=groovy|file=src/site/examples.txt} 65 | 66 | -------------------------------------------------------------------------------- /src/site/apt/doc/httpurlclient.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | HttpURLClient 3 | ------ 4 | ------ 5 | ------ 6 | 7 | HttpURLClient 8 | 9 | HTTPURLClient is an alternate implementation that does not rely on HttpClient 10 | sockets for communication. The main motivation behind this is use in 11 | {{{http://code.google.com/appengine/docs/java/overview.html}Google App Engine}}. 12 | GAE does not allow direct socket communication; instead web resources 13 | are accessed only via the <<>> class. Since 14 | <<>> is somewhat cumbersome to use (particularly for REST 15 | operations,) <<>> applies some of HTTPBuilder's idioms on top 16 | of an HttpURLConnection. 17 | 18 | HttpURLClient supports automatic content-type parsing and request marshalling, 19 | URI manipulation and the convenient header access. 20 | 21 | Here is an example of reading London weather using both JSON and XML: 22 | 23 | %{code-snippet|id=urlclient1|brush=groovy|file=src/site/examples.txt} 24 | -------------------------------------------------------------------------------- /src/site/apt/doc/index.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | Documentation 3 | ------ 4 | ------ 5 | ------ 6 | 7 | Introduction 8 | 9 | HTTPBuilder is optimized for three tasks: 10 | 11 | [[]] Streaming response handling, 12 | 13 | [[]] Automatic response parsing based on content-type 14 | 15 | [[]] Sucess & failure handling based on the response status code 16 | 17 | [] 18 | 19 | To achieve this, HTTPBuilder exposes convenience methods for common request types, 20 | (i.e. GET and POST) and a more complex <<>> method that allows fine-grained 21 | configuration of each request and response. 22 | 23 | The following example shows most of the <<>> method's features. 24 | 25 | %{code-snippet|id=doc1|brush=groovy|file=src/site/examples.txt} 26 | 27 | 28 | If you are familiar with the HTTP protocol, most of the above should be 29 | self-explanatory. The 30 | <<<{{{../apidocs/groovyx/net/http/HTTPBuilder.html#request(groovyx.net.http.Method,%20java.lang.Object,%20groovy.lang.Closure)}request()}}>>> 31 | method accepts three parameters: a , 32 | , and a . Within the 33 | configuration closure, we can fine-tune the parameters for this request, 34 | including manipulating portions of the URL, and adding request headers. A 35 | full list of properties that can be manipulated during request configuration 36 | can be found in the 37 | {{{../apidocs/groovyx/net/http/HTTPBuilder.RequestConfigDelegate.html}RequestConfigDelegate}} 38 | class. 39 | 40 | The <<>> parameter passed to the closure is an 41 | {{{http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/message/BasicHttpRequest.html}HttpRequest}} 42 | instance, which can be used to configure additional aspects of the request 43 | for which HTTPBuilder does not provide a wrapper. 44 | 45 | The remaining 'response' definitions in the above example configure how the 46 | response is handled based on the HTTP status code returned by the server. In 47 | general, a "success" and "failure" response handler may be defined, or a 48 | response handler may be defined for a specific response code. 49 | 50 | In the simplest case, no 'failure' handler needs to be defined; the default 51 | failure handler will throw an exception, which is triggered for any response 52 | code > 399. You can also customize default response handlers that will be 53 | in effect for all requests made by the <<>> instance: 54 | 55 | %{code-snippet|id=doc2|brush=groovy|file=src/site/examples.txt} 56 | 57 | 58 | The Magic: Built-in Content-Type Parsing 59 | 60 | HTTPBuilder is also able to intelligently handle different response 61 | content-types. In this example, the response data is automatically parsed 62 | into a <<>> object, which is then passed to the 'success' response 63 | handler. 64 | 65 | %{code-snippet|id=doc3|brush=groovy|file=src/site/examples.txt} 66 | 67 | 68 | You can try the above example by adding the <<<@Grab>>> macro (as demonstrated 69 | on the {{{../download.html}download page}}) with Groovy 1.6. 70 | 71 | 72 | *Parser Resolution 73 | 74 | By default, new HTTPBuilder instances use <<>> as the initial 75 | content-type. This means every request has an <<>> header, and 76 | it is up to the server to decide what appropriate content-type to return. 77 | HTTPBuilder then chooses the appropriate parser from its <<>> 78 | based on the response <<>> header value. 79 | 80 | a <<>> is set (either in 81 | <<>> or as a <<>> method parameter), 82 | HTTPBuilder will attempt to parse the response using that content-type, 83 | regardless of what the server actually responds with. The first example on 84 | this page actually uses that strategy to parse an HTML document as plain text 85 | (i.e. by creating a <<>>) rather than detecting an HTML content-type 86 | and using the default HTML parsing method. You can read more about automatic 87 | content-type parsing on the {{{./contentTypes.html}Content Types page}}. 88 | 89 | 90 | 91 | {Logging and Debugging} 92 | 93 | Probably the quickest way to debug is to turn on logging for HTTPBuilder and 94 | Apache HttpClient. An example 95 | {{{https://svn.codehaus.org/gmod/httpbuilder/trunk/src/test/resources/log4j.xml} 96 | log4j configuration}} can be used to output headers and request/response 97 | content, as well as additional information on what HTTPBuilder is doing with 98 | the request and response. 99 | 100 | To see the raw request and response output, change the Apache HttpClient 101 | logging parameters to <<>> like so: 102 | 103 | %{code-snippet|id=doc4|brush=xml|file=src/site/examples.txt} 104 | -------------------------------------------------------------------------------- /src/site/apt/doc/json.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | JSON 3 | ------ 4 | ------ 5 | ------ 6 | 7 | Parsing JSON data 8 | 9 | JSON responses can be automatically handled by HTTPBuilder's default 10 | content parser registry. It uses {{{http://json-lib.sourceforge.net/}JSON-Lib}} 11 | internally to parse the response data into a <<>>, in a similar 12 | manner used by Groovy's <<>>. 13 | 14 | The following is a working example (try it!) of accessing a Twitter feed using 15 | HTTPBuilder and JSON. 16 | 17 | %{code-snippet|id=json1|brush=groovy|file=src/site/examples.txt} 18 | 19 | 20 | POSTing JSON data 21 | 22 | JSON can just as easily be sent in the body of a <<>> or <<>> 23 | request. The JSON request encoder can convert a Map, List, POJO, or a closure. 24 | 25 | The simplest method is to use a map or list like so: 26 | 27 | %{code-snippet|id=json2|file=src/site/examples.txt} 28 | 29 | Thanks to Json-Lib's 30 | {{{http://json-lib.sourceforge.net/apidocs/net/sf/json/groovy/JsonGroovyBuilder.html}JsonGroovyBuilder}} 31 | class, we an also build the request data on-the-fly like so: 32 | 33 | %{code-snippet|id=json3|brush=groovy|file=src/site/examples.txt} 34 | 35 | See the {{{../apidocs/groovyx/net/http/EncoderRegistry.html#encodeJSON(java.lang.Object)} 36 | API documentation}} for more details. 37 | -------------------------------------------------------------------------------- /src/site/apt/doc/post.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | POST Examples 3 | ------ 4 | ------ 5 | ------ 6 | 7 | 8 | Simple POST 9 | 10 | HTTPBuilder defines a {{{../apidocs/groovyx/net/http/HTTPBuilder.html#post(java.util.Map,%20groovy.lang.Closure)}<<>> 11 | convenience method}}, which allows for easily POSTing data as an HTML form: 12 | 13 | %{code-snippet|id=post1|brush=groovy|file=src/site/examples.txt} 14 | 15 | Similar to the <<>> convenience method, <<>> accepts the 16 | options as named parameters, and takes a closure that is called as the 17 | 'success' response handler. There is also a <<>> variant that does not 18 | require a response handler closure; in this case, the builder instance's 19 | success handler is used, which by default will return the parsed response 20 | data. 21 | 22 | <> 23 | HTTPBuilder's <<>> method is special in particular because it 24 | assumes the request body will be URL-encoded form data. This is the only method 25 | that assumes a particular request content-type. For all methods that send a 26 | request body (including <<>>,) a <<>> parameter may be 27 | set to explicitly define how the request data should be serialized. In these 28 | cases, if the request content-type is not specified, it will default to the 29 | response <<>>. Supported request content-types are handled by the 30 | <<<{{{../apidocs/index.html?groovyx/net/http/EncoderRegistry.html}EncoderRegistry}}>>> 31 | class. 32 | 33 | 34 | 35 | POSTing with the <<>> Method 36 | 37 | This example is equivalent to the above, using the <<>> method: 38 | 39 | %{code-snippet|id=post2|brush=groovy|file=src/site/examples.txt} 40 | 41 | In the above example, <<>>, <<>>, <<>> and 42 | <<>> are properties of the 43 | {{{../apidocs/groovyx/net/http/HTTPBuilder.RequestConfigDelegate.html}closure delegate}} 44 | so they are already defined. 45 | 46 | For cases where the request content-type is necessarily different than the 47 | response, the request configuration delegate has a 48 | <<<{{{../apidocs/groovyx/net/http/HTTPBuilder.RequestConfigDelegate.html#send(java.lang.Object,%20java.lang.Object)}send()}}>>> 49 | method which can be used set the request content-type and data at the same time: 50 | 51 | 52 | %{code-snippet|id=post3|brush=groovy|file=src/site/examples.txt} 53 | -------------------------------------------------------------------------------- /src/site/apt/doc/rest.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | REST 3 | ------ 4 | ------ 5 | ------ 6 | 7 | 8 | 9 | RESTClient 10 | 11 | RESTClient is an extension of HTTPBuilder, which makes a few concessions in 12 | HTTPBuilder's flexibility in order to make REST operations as simple as 13 | possible. 14 | 15 | RESTClient makes great use of the automatic content-type parsing 16 | and encoding which makes working with XML and JSON extremely easy, both in the 17 | request and response side. It also adds some additional convenience methods 18 | for response header parsing. 19 | 20 | The main advantages of RESTClient are: 21 | 22 | [[]] RESTClient has convenience methods for <<>>, <<>> <<>> 23 | <<>>, <<>> 24 | 25 | [[]] The response data is always parsed and buffered in-memory 26 | 27 | [[]] The returned <<<{{{../apidocs/groovyx/net/http/HttpResponseDecorator.html}HttpResponseDecorator}}>>> 28 | instance gives convenient access to headers and the parsed response data 29 | 30 | [[]] No user-defined closure is needed 31 | 32 | 33 | * Examples 34 | 35 | All of these examples use the {{{http://apiwiki.twitter.com/Twitter-API-Documentation}Twitter REST API.}} 36 | 37 | ** Test a URL using the <<>> method 38 | 39 | %{code-snippet|id=rest1|brush=groovy|file=src/site/examples.txt} 40 | 41 | The above example takes advantage of HTTPBuilder's default failure handler, 42 | which will cause an exception to be thrown for any 'failed' response. That 43 | exception will still allow access to details of the response (e.g. the 44 | response status or message). 45 | 46 | 47 | ** <<>> our friends' timeline 48 | 49 | %{code-snippet|id=rest2|brush=groovy|file=src/site/examples.txt} 50 | 51 | All request parameters are defined 52 | {{{../apidocs/groovyx/net/http/HTTPBuilder.RequestConfigDelegate.html#setPropertiesFromMap(java.util.Map)}here}}. 53 | 54 | The <<>> field in the above example is an instance of <<>>. 55 | Calling <<>> returns the parsed response content. This is the 56 | same parsed response that you would get passed to HTTPBuilder's response 57 | handler closure, but it is always buffered in-memory and the response stream 58 | is automatically closed. 59 | 60 | 61 | ** <<>> a status update to Twitter! 62 | 63 | %{code-snippet|id=rest3|brush=groovy|file=src/site/examples.txt} 64 | 65 | Note that the above example is posting the request data as 66 | <<>>. (The twitter API doesn't support XML or 67 | JSON POST requests.) For this reason, a <<>> parameter must 68 | be specified in order to identify how the request body should be serialized. 69 | 70 | Since we never set a default content-type on the RESTClient instance or pass a 71 | <<>> argument in this request, RESTClient will put <<>> 72 | in the request header, and parse the response based on whatever is given in 73 | the response content-type header. So because Twitter correctly identifies its 74 | response as <<>>, it will automatically be parsed by the 75 | default {{{../apidocs/groovyx/net/http/ParserRegistry.html#parseJSON(org.apache.http.HttpResponse)}JSON parser}}. 76 | 77 | 78 | ** Now <<>> that post 79 | 80 | %{code-snippet|id=rest4|brush=groovy|file=src/site/examples.txt} 81 | 82 | -------------------------------------------------------------------------------- /src/site/apt/doc/ssl.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | SSL 3 | ------ 4 | ------ 5 | ------ 6 | 7 | SSL Configuration 8 | 9 | SSL should, for the most part, "just work." There are a few situations where 10 | it is not completely intuitive. You can follow the examples below, or see HttpClient's 11 | {{{http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/conn/ssl/SSLSocketFactory.html}SSLSocketFactory documentation}} 12 | for more information. 13 | 14 | 15 | * SSLPeerUnverifiedException 16 | 17 | If you can't connect to an SSL website, it is likely because the certificate 18 | chain is not trusted. This is an Apache HttpClient issue, but explained here 19 | for convenience. To correct the untrusted certificate, you need to import a 20 | certificate into an SSL truststore. 21 | 22 | First, export a certificate from the website using your browser. For example, 23 | if you go to in Firefox, you will probably get a warning 24 | in your browser. Choose "Add Exception," "Get Certificate," "View," 25 | "Details tab." Choose a certificate in the chain and export it as a PEM file. 26 | You can view the details of the exported certificate like so: 27 | 28 | ----------------------- 29 | $ keytool -printcert -file EquifaxSecureGlobaleBusinessCA-1.crt 30 | Owner: CN=Equifax Secure Global eBusiness CA-1, O=Equifax Secure Inc., C=US 31 | Issuer: CN=Equifax Secure Global eBusiness CA-1, O=Equifax Secure Inc., C=US 32 | Serial number: 1 33 | Valid from: Mon Jun 21 00:00:00 EDT 1999 until: Sun Jun 21 00:00:00 EDT 2020 34 | Certificate fingerprints: 35 | MD5: 8F:5D:77:06:27:C4:98:3C:5B:93:78:E7:D7:7D:9B:CC 36 | SHA1: 7E:78:4A:10:1C:82:65:CC:2D:E1:F1:6D:47:B4:40:CA:D9:0A:19:45 37 | Signature algorithm name: MD5withRSA 38 | Version: 3 39 | .... 40 | ----------------------- 41 | 42 | Now, import that into a Java keystore file: 43 | 44 | ----------------------- 45 | $ keytool -importcert -alias "equifax-ca" -file EquifaxSecureGlobaleBusinessCA-1.crt \ 46 | > -keystore truststore.jks -storepass test1234 47 | Owner: CN=Equifax Secure Global eBusiness CA-1, O=Equifax Secure Inc., C=US 48 | Issuer: CN=Equifax Secure Global eBusiness CA-1, O=Equifax Secure Inc., C=US 49 | Serial number: 1 50 | Valid from: Mon Jun 21 00:00:00 EDT 1999 until: Sun Jun 21 00:00:00 EDT 2020 51 | Certificate fingerprints: 52 | MD5: 8F:5D:77:06:27:C4:98:3C:5B:93:78:E7:D7:7D:9B:CC 53 | SHA1: 7E:78:4A:10:1C:82:65:CC:2D:E1:F1:6D:47:B4:40:CA:D9:0A:19:45 54 | Signature algorithm name: MD5withRSA 55 | Version: 3 56 | ... 57 | Trust this certificate? [no]: yes 58 | Certificate was added to keystore 59 | ----------------------- 60 | 61 | Now you want to use this truststore in your client: 62 | 63 | %{code-snippet|id=ssl1|brush=groovy|file=src/site/examples.txt} 64 | 65 | 66 | 67 | * ignoreSSLIssues 68 | 69 | Alternatively there is a convenience method to ignore SSL issues that arise from 70 | untrusted certificates as well as hostname mismatches. This method is ideally suited 71 | for dev and test situations where strict SSL usage is too time-consuming to configure. 72 | 73 | %{code-snippet|id=ssl2|brush=groovy|file=src/site/examples.txt} 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/site/apt/doc/uribuilder.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | URIBuilder 3 | ------ 4 | ------ 5 | ------ 6 | 7 | The {{{../apidocs/groovyx/net/http/URIBuilder.html}URIBuilder}} class is 8 | extremely useful for URI and URL construction and manipulation. It is used 9 | within HTTPBuilder, but it is also useful even from plain Java code. 10 | 11 | If you've ever attempted to change a URL relative to its current path, 12 | you've probably noticed that using <<>> strips off everything 13 | after the last '/' of the URI, including any 'document,' query string and 14 | fragment. URIBuilder aims to make URI manipulation a easier and 15 | allows you to manipulate aspects of the URL while maintaining the other pieces: 16 | 17 | 18 | %{code-snippet|id=uri1|brush=groovy|file=src/site/examples.txt} 19 | 20 | 21 | Query parameters can also be easily manipulated as a map: 22 | 23 | %{code-snippet|id=uri2|brush=groovy|file=src/site/examples.txt} 24 | 25 | 26 | As mentioned above, URIBuilder is also useful from Java. This is because 27 | URIBuilder's setter methods return the modified builder instance, to allow 28 | for the 'fluent interface' pattern. The above example could be written 29 | in Java as follows: 30 | 31 | %{code-snippet|id=uri3|brush=groovy|file=src/site/examples.txt} 32 | 33 | URIBuilder also has methods for coercion to a URI or URL instance. Since 34 | URIBuilder is mutable, it generally should <> be used for things like 35 | map keys. In this case, the builder's current state should be frozen by 36 | coercing it to a URI or URL. 37 | -------------------------------------------------------------------------------- /src/site/apt/doc/xml.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | XML 3 | ------ 4 | ------ 5 | ------ 6 | 7 | Parsing XML data 8 | 9 | By default, HTTPBuilder classes will automatically parse XML responses using 10 | an <<<{{{http://groovy.codehaus.org/gapi/groovy/util/XmlSlurper.html}XmlSlurper}}>>>. 11 | 12 | You can try the following example in the Groovy console (Groovy 1.6+ is needed 13 | for the <<<@Grab>>> macro): 14 | 15 | %{code-snippet|id=xml1|brush=groovy|file=src/site/examples.txt} 16 | 17 | HTTPBuilder will automatically detect the content-type (assuming the sends the 18 | correct response header) and parse the response as XML. It is not necessary 19 | (but optional) to explicitly specify the <<>> 20 | parameter. 21 | 22 | <> response data will also be parsed automatically, by using 23 | {{{http://nekohtml.sourceforge.net/}NekoHTML}} which corrects the XML stream 24 | before it is passed to the <<>>. The resulting behavior is that 25 | you can parse HTML as if it was well-formed XML. 26 | 27 | 28 | * DTDs, Schemas and Entities 29 | 30 | Keep in mind that particularly when parsing HTML documents, they often refer 31 | to external DTDs. The required behavior of all JAXP XML parsers is to 32 | retrieve and parse any referenced entities (e.g. DTD, schema, etc.) 33 | the document is processed (yes, even if validation is disabled.) This can 34 | become costly when the referenced entity document never changes. 35 | 36 | To avoid the overhead of downloading and parsing externally referenced documents 37 | for every request, the HTTPBuilder's built-in XML and HTML parser uses an 38 | {{{http://xml.apache.org/commons/components/resolver/resolver-article.html}XML Catalog}} 39 | to store a local copy of frequently used DTD and entity definitions. You can 40 | {{{../apidocs/groovyx/net/http/ParserRegistry.html#addCatalog(java.net.URL)}add additional entity files}} 41 | to the default parser's catalog as well. 42 | 43 | 44 | POSTing XML data 45 | 46 | XML data is serialized using 47 | <<<{{{http://groovy.codehaus.org/gapi/index.html?groovy/xml/StreamingMarkupBuilder.html}StreamingMarkupBuilder}}>>>. 48 | You can define the <<>> property as a closure like so: 49 | 50 | %{code-snippet|id=xml2|brush=groovy|file=src/site/examples.txt} 51 | 52 | The body is then transformed to an XML string by 53 | <<<{{{../apidocs/groovyx/net/http/EncoderRegistry.html#encodeXML(java.lang.Object)}EncoderRegistry.encodeXML()}}>>>. 54 | Alternatively, the XML body may be passed as a raw string as well. 55 | 56 | 57 | 58 | Reading an XML response as plain text 59 | 60 | Another common request is <"What if I want to display the raw XML rather than 61 | parse it?"> 62 | 63 | In order to do that, you're going to send a <<>> parameter, 64 | to force HTTPBuilder (or RESTClient) to use the <<>> parser. However, 65 | since setting the <<>> also affects the <<>> 66 | request header, we might need to override that as well. 67 | 68 | For Example: 69 | 70 | %{code-snippet|id=xml3|brush=groovy|file=src/site/examples.txt} 71 | 72 | Furthermore, you can use HTTPBuilder's defaults to reduce the number of 73 | parameters passed to each request method like so: 74 | 75 | %{code-snippet|id=xml4|brush=groovy|file=src/site/examples.txt} 76 | -------------------------------------------------------------------------------- /src/site/apt/download.apt.vm: -------------------------------------------------------------------------------- 1 | ------ 2 | Download 3 | ------ 4 | ------ 5 | ------ 6 | 7 | Project Archive and JAR 8 | 9 | The latest JAR can be obtained from the Codehaus Maven repository. The 10 | repository also contains project assemblies (.tar.gz), which include the 11 | source code, generated JavaDoc, and dependency JARs. <> 13 | 14 | {{{http://snapshots.repository.codehaus.org/org/codehaus/groovy/modules/http-builder/http-builder/} 15 | Browse the latest snapshot here.}} 16 | 17 | {{{http://repository.codehaus.org/org/codehaus/groovy/modules/http-builder/http-builder/} 18 | Browse the latest stable build here.}} 19 | 20 | Older releases may also be found under the 21 | {{{http://repository.codehaus.org/org/codehaus/groovy/http-builder/}old group ID.}} 22 | 23 | Maven Users 24 | 25 | Maven users can simply copy the dependency tag into their own project: 26 | 27 | %{code-snippet|id=download1|brush=xml|file=src/site/examples.txt} 28 | 29 | You might also need to add the Codehaus repository to your project POM as well: 30 | 31 | %{code-snippet|id=download2|brush=xml|file=src/site/examples.txt} 32 | 33 | 34 | Using Grape 35 | 36 | {{{http://groovy.codehaus.org/Grape}Grape}} is a neat dependency management 37 | feature in Groovy 1.6+ that allows you to download and add JARs to your classpath at runtime. 38 | 39 | You can automatically put HTTPBuilder in your classpath by using the following 40 | annotation on a method in your code: 41 | 42 | %{code-snippet|id=download3|brush=groovy|file=src/site/examples.txt} 43 | 44 | Note that in Groovy 1.6, the <<<@Grab>>> annotation must occur on a method 45 | definition (it can be an empty method.) Putting the annotation before a 46 | statement such as a variable declaration will not work. This is a common 47 | error and Grape will not provide any indication that it is not augmenting 48 | the runtime classpath. This should change in Groovy 1.7. 49 | 50 | If you are still having problems, try calling <<>> from the command 51 | line like so: 52 | 53 | ---------------------------------------- 54 | $ grape resolve org.codehaus.groovy.modules.http-builder http-builder ${project-version} 55 | # will tell you where grape is looking and give detailed config information 56 | 57 | 58 | $ grape install org.codehaus.groovy.modules.http-builder http-builder ${project-version} 59 | # will attempt to download the JAR and transitive dependencies 60 | ---------------------------------------- 61 | 62 | The above commands will tell you which repository URLs grape is trying to download 63 | from if it cannot find the artifact. 64 | 65 | If you are trying to use a snapshot version of HTTPBuilder, you might also 66 | need to create the following file in <<<~/.groovy/grapeConfig.xml>>>: 67 | 68 | %{code-snippet|id=download4|brush=xml|file=src/site/examples.txt} 69 | 70 | Note the line, which tells Grape where HTTPBuilder's 71 | snapshot builds are located, since that repository is not searched by default. 72 | -------------------------------------------------------------------------------- /src/site/apt/home.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | Home 3 | ------ 4 | ------ 5 | ------ 6 | 7 | HTTPBuilder {Overview} 8 | 9 | HTTPBuilder is the easiest way to manipulate HTTP-based resources from the JVM. 10 | 11 | In a nutshell, HTTPBuilder is a wrapper for Apache's 12 | {{{http://hc.apache.org/httpcomponents-client-ga/}HttpClient}}, with some (actually, 13 | a lot of) Groovy syntactical sugar thrown on top. The request/response model is 14 | also inspired by Prototype.js' 15 | {{{http://prototypejs.org/api/ajax/request}Ajax.Request}}. 16 | 17 | In short, HTTPBuilder allows you to make HTTP requests like this: 18 | 19 | 20 | %{code-snippet|id=overview1|brush=groovy|file=src/site/examples.txt} 21 | 22 | 23 | But it actually goes much further to handle common tasks such as building 24 | and parsing common content-types, handling common content-encodings, and 25 | built-in support for common authentication mechanisms. It works equally as 26 | well for simple REST-based requests, or ad-hoc web downloads. 27 | 28 | 29 | {Features} 30 | 31 | * Builder and parser support for {{{./doc/xml.html}XML}}, {{{./doc/json.html}JSON}}, and HTML 32 | 33 | * Easy {{{./doc/uribuilder.html}URI manipulation}} 34 | 35 | * Streamlined {{{./doc/rest.html}client for REST}} interfaces 36 | 37 | * Built-in support for GZIP and Deflate content-encoding 38 | 39 | * Built-in support for most {{{./doc/auth.html}common authentication schemes}} 40 | 41 | * Status code based {{{./doc/handlers.html}response handling}} 42 | 43 | * Convenience methods for {{{./doc/get.html}GET}} and {{{./doc/post.html}POST}} 44 | 45 | * {{{./doc/httpurlclient.html}Compatible}} with Google App Engine 46 | 47 | * {{{./doc/async.html}AsyncHTTPBuilder}} for asynchronous requests 48 | 49 | * Easily extensible API 50 | 51 | 52 | {Components} 53 | 54 | {{{./apidocs/groovyx/net/http/HTTPBuilder.html}HTTPBuilder}} is the main 55 | API class which is used to make requests and parse responses. 56 | {{{./apidocs/groovyx/net/http/AsyncHTTPBuilder.html}AsyncHTTPBuilder}} is a 57 | subclass of the base HTTPBuilder which transparently delegates all requests 58 | to a thread pool for execution. {{{./apidocs/groovyx/net/http/RESTClient.html}RESTClient}} 59 | extends HTTPBuilder to eliminate the closure definition, to make REST 60 | operations particularly easy. Finally, 61 | {{{./apidocs/groovyx/net/http/HttpURLClient.html}HttpURLClient}} provides most of 62 | HTTPBuilder's intelligent handling in a package that can be used from 63 | Google App Engine. 64 | 65 | {{{./apidocs/groovyx/net/http/URIBuilder.html}URIBuilder}} provides a fluent 66 | interface for manipulating complex URLs. It is also used internally by 67 | HTTPBuilder to handle path and query string modification. 68 | 69 | See the {{{./apidocs/index.html}JavaDoc}} for full documentation. 70 | 71 | 72 | {Requirements} 73 | 74 | * At least Java 1.5. This is because HttpClient 4 requires Java 5. 75 | 76 | * Groovy 1.5 or later, although it should work with earlier versions 77 | 78 | * JAR dependencies can be found in the packaged distributions linked from the 79 | {{{./download.html}download page}}. 80 | 81 | {Support} 82 | 83 | If you are having trouble, {{{./doc/index.html#Logging_and_Debugging}start here}}. 84 | 85 | The best place to ask for help would be the 86 | {{{http://xircles.codehaus.org/lists/user@groovy.codehaus.org}Groovy-User mailing list.}} 87 | If you do not want to subscribe to the mailing list, you can also post questions 88 | through {{{http://www.nabble.com/groovy---user-f11867.html}Nabble.}} See the 89 | {{{./about.html}About page}} for additional contact information. 90 | 91 | Please report any problems or errors to the mailing list (or 92 | {{{http://jira.codehaus.org/browse/GMOD/component/13625}JIRA}}) and it should 93 | be resolved quickly. 94 | -------------------------------------------------------------------------------- /src/site/apt/index.apt: -------------------------------------------------------------------------------- 1 | ------ 2 | News 3 | ------ 4 | ------ 5 | ------ 6 | 7 | 8 | 9 | News 10 | 11 | * 25 Feb 2014 - 0.7.1 released 12 | 13 | A minor release with a couple pull requests from the community. 14 | 15 | Looking forward, release 0.8 will likely contain an upgrade to HttpClient 4.3 and migrate to Groovy's native JsonBuilder for sending JSON. Both of these changes have the potential to introduce backwards incompatibilities. 16 | 17 | * 4 Feb 2014 - 0.7 released 18 | 19 | A long overdue release containing a number of community contributions. For a full list see the {{{./changes.html}Changes}} page. 20 | 21 | The dependency on Groovy is now 'provided' scope. If you were previously relying on HTTPBuilder to pull Groovy into the dependency tree, you will now need to explicitly import it. 22 | 23 | * 16 Oct 2012 - 0.6.0 released, source now hosted on Github 24 | 25 | The 0.6.0 release is now available which takes care of upgrading some older dependencies. 26 | 27 | The source code is now {{{https://github.com/jgritman/httpbuilder}hosted on Github}} 28 | 29 | * 26 Dec 2011 - 0.5.2 finally finally released for reals 30 | 31 | With so many other projects, HTTPBuilder has taken a back seat, to say the least. 32 | Well, I've finally made 0.5.2 official, so folks relying on snapshots can now 33 | use a release version. I hope to turn around 0.5.3 quickly to address a couple 34 | important issues like the Groovy range dependency, SSL host verification, 35 | preemptive auth and maybe whitespace URI parameter encoding. 36 | 37 | Stay tuned and watch for snapshots! 38 | 39 | * 30 Sept 2010 - 0.5.1 with OAuth Support! 40 | 41 | I've been meaning to implement this for a while now, especially since Twitter 42 | made the switch, and I use the Twitter API for a lot of unit tests. Now, 43 | {{{http://oauth.net}OAuth}} is supported in HTTPBuilder 0.5.1 thanks to the 44 | {{{http://code.google.com/p/oauth-signpost/}Signpost framework}}. The 45 | implementation isn't perfect due to 46 | {{{http://issues.apache.org/jira/browse/HTTPCLIENT-901}a limitation}} in 47 | HttpClient 4.0. However, once HttpClient 4.1 is released, I expect to update 48 | the implementation which should be transparent to most users. 49 | 50 | I did my best to ensure this release is binary compatible with version 0.5.0. 51 | While 0.5.1 has an dependency on Signpost, if you are not using 52 | OAuth to sign your requests, you should not need the Signpost JARs on your 53 | classpath. No Signpost classes are loaded until you indicate that you want to 54 | OAuth sign your requests. 55 | 56 | Want to see the API changes? 57 | <<<{{{./apidocs/groovyx/net/http/AuthConfig.html#oauth(java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String)}HTTPBuilder.auth.oauth()}}>>> 58 | and 59 | <<<{{{./apidocs/groovyx/net/http/HttpURLClient.html#setOAuth(java.lang.Object,%20java.lang.Object,%20java.lang.Object,%20java.lang.Object)}HttpURLClient.setOAuth()}}>>>. 60 | 61 | * 3 May 2010 - 0.5.0 Released (finally!) 62 | 63 | I've pushed 0.5.0 out the door! It's the same as RC3, so if you've been using 64 | that you should be able to just change the version number and you're good to 65 | go. I'm already working on some minor improvements for a minor release (0.5.1) 66 | but they didn't merit being squeezed into the 0.5.0 release since it's already 67 | been delayed so long. Enjoy! 68 | 69 | * 31 Mar 2010 - Problems with Grape/ Grab 70 | 71 | So I had a couple people mention that they had trouble using <<>> to 72 | download RC3 due to a bad checksum on the POM file. I tried re-deploying it, 73 | (thinking it was a bad upload) and now I'm getting a _bunch_ of people saying 74 | they can't download it! 75 | 76 | As a work-around, please add the following line to <<<~/.groovy/grapeConfig.xml>>>: 77 | <<<>>>. This should allow Grape to 78 | ignore the checksum and download the file, until I've got the checksum problem 79 | resolved. Stay tuned! 80 | 81 | * 17 Feb 2010 - 0.5.0-RC3 Released 82 | 83 | I've been extremely lax in getting 0.5.0 out the door, but a couple minor 84 | bugs have been uncovered in the mean time. All known defects are fixed at 85 | this point, and I'm planning on releasing 0.5.0 final in the next week or so, 86 | provided no new defects are uncovered! 87 | 88 | Please test out RC3 and stay tuned! 89 | 90 | * 16 Oct 2009 - Added Ohloh Stats 91 | 92 | So I added this project to Ohloh, which found HTTPBuilder to be "Extremely 93 | well commented," :) but also a "small development team." :( The stats are a 94 | little off too, since it has included the HTML and XHTML DTDs which are in 95 | source control, but I obviously did not write them... 96 | 97 | On a related note, if the Ohloh animated GIF on the sidebar 98 | particularly annoys anyone, just complain and I can remove it. Although I do 99 | like how it brags about the project :) 100 | 101 | 102 | * 06 Oct 2009 - More Snapshots & important changes... 103 | 104 | So no matter how hard I try, I can't seem to release 0.5.0. More accurately, 105 | a few important issues came up that I didn't want to fix in a 0.6 branch 106 | immediately after a 0.5.0 release. So my solution instead is to introduce some 107 | late changes... 108 | 109 | Probably the most important change is URIBuilder's handling of query parameters. 110 | Previously, URIBuilder only allowed a single value per-parameter. That is, 111 | it was impossible to do <<>> because the Map literal does 112 | not allow duplicate keys. Well, now you can pass a list of values like so: 113 | <<>> which will add two values for 114 | <<>>. Of course, this behavior merits a change to the <<>> 115 | method as well. 116 | 117 | As a result, <<>> and <<>> 118 | will not replace any existing parameters with the same name; instead it will 119 | add to a multi-valued list for the given parameter. Parameters with single 120 | values will remain as strings, not lists. 121 | 122 | * 16 June 2009 - 0.5.0 RC1! 123 | 124 | RC1 is released! Go {{{./download.html}download it}}! 125 | 126 | Keep in mind there are a few breaking changes from 0.4.1. See the 127 | {{{http://jira.codehaus.org/browse/GMOD/fixforversion/15065}JIRA report}} 128 | for a full list of changes. Some breaking changes include: 129 | 130 | [[]] New Maven group - <<>> 131 | 132 | [[]] HTTPBuilder.(get|post) named arguments - <<>> is now <<>> 133 | 134 | [[]] The HTTPBuilder class' <<>> property has been renamed to <<>> 135 | 136 | [] 137 | 138 | Of course the 0.5.0 release has a ton of more changes; read below 139 | for most of them... 140 | 141 | 142 | * 13 June 2009 - Release Candidate (almost...) 143 | 144 | I was just about to release version 0.5.0-RC1 and announce it on the mailing 145 | list, when suddenly my unit tests started failing! <<>> ... What? 147 | Turns out, I got bit by the {{{http://www.w3.org/blog/systeam/2008/02/08/w3c_s_excessive_dtd_traffic} 148 | W3C trying frantically to conserve their bandwidth}}. 149 | 150 | So realizing this is something that should really be built in, I added the capability via Apache's {{{http://xml.apache.org/commons/components/resolver/} 151 | XML Resolver}}. All HTML4 and XHTML DTDs are now cached in a 152 | {{{http://fisheye.codehaus.org/browse/gmod/httpbuilder/trunk/src/main/resources/catalog/html.xml?r=root:} 153 | default catalog}}, so parsing any sites that reference those DTDs should see a 154 | significant performance improvement now. 155 | 156 | 157 | 158 | * 29 May 2009 159 | 160 | The 0.5.0 release is almost feature complete; see 161 | {{{http://jira.codehaus.org/browse/GMOD/fixforversion/15065}JIRA}} for a list 162 | of issues that have been gone into this version. One of the major changes in 163 | today's snaphot release is deprecation of the 'params' named parameter. 164 | 165 | For HTTPBuilder operations that take name parameters, <<>> has been 166 | replaced by <<>> to set URL query parameters. This is to more clearly 167 | differentiate between URL and form POST parameters, and to avoid possible 168 | collision with future convenience methods for Apache HttpClient 'params' 169 | configuration. 170 | 171 | Please test out the latest snapshot; if I don't hear any bug reports in a week 172 | or two, I'll release v0.5.0. 173 | 174 | * 27 Apr 2009 175 | 176 | <> The project's group ID has changed to conform with 177 | the standard for Groovy projects hosted on Codehaus. The new group ID is 178 | <>. 179 | 180 | The 0.5 release is still pending, but I have an exciting new feature - 181 | <>. This may seem redundant, since its features are almost 182 | identical to HTTPBuilder and RESTClient. The kicker though is that this class 183 | works with Google App Engine! 184 | 185 | GAE's Java sandbox doesn't allow socket usage, which totally kills Apache 186 | HttpClient (and hence, HB). This class uses HttpURLConnection for all I/O, 187 | enabling most of the conveniences of HTTPBuilder within GAE. Try it out 188 | in the latest HTTPBuilder snapshot. 189 | 190 | * 30 Mar 2009 191 | 192 | Documentation is in the process of being overhauled to provide more 193 | comprehensive API examples. I've published a version that is still in 194 | progress, but certainly better than the single document that I had previously. 195 | 196 | * 11 Mar 2009 197 | 198 | The latest 0.5 version now includes a new RESTClient class for even easier 199 | REST-style operations without a user-defined closure! See 200 | {{{http://fisheye.codehaus.org/browse/gmod/httpbuilder/trunk/src/main/script/twitter_restbuilder.groovy?r=root:} 201 | the Twitter example}}, and the updated {{{./apidocs/groovyx/net/http/RESTClient.html}JavaDoc}}. 202 | 203 | * 04 Mar 2009 204 | 205 | Now you can follow us on {{{http://twitter.com/httpbuilder}Twitter!}} 206 | HTTPBuilder now 'tweets' its releases as an automated part of the build 207 | process. This is a neat proof-of-concept that could easily be added to any 208 | Maven build thanks to 209 | {{{http://groovy.codehaus.org/GMaven+-+Executing+Groovy+Code#GMaven-ExecutingGroovyCode-ExecuteaLocalGroovyScript}GMaven.}} 210 | See the 211 | {{{http://fisheye.codehaus.org/browse/gmod/httpbuilder/trunk/pom.xml?r=root:#l122}POM}} 212 | and 213 | {{{http://fisheye.codehaus.org/browse/gmod/httpbuilder/trunk/src/main/script/release_tweet.groovy?r=root:}tweet script}} 214 | for details. 215 | -------------------------------------------------------------------------------- /src/site/resources/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgritman/httpbuilder/72fb50a94cbd85f7cde17be0ea6b8e8375bc573e/src/site/resources/images/logo.png -------------------------------------------------------------------------------- /src/site/resources/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 21 | 23 | 27 | 31 | 32 | 39 | 48 | 49 | 69 | 71 | 72 | 74 | image/svg+xml 75 | 77 | 78 | 79 | 80 | 84 | HTTP 99 | Builder 113 | 121 | 129 | 139 | 149 | 159 | 169 | 179 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /src/site/site.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | HTTPBuilder 9 | images/logo.png 10 | index.html 11 | 12 | 13 | Groovy 14 | http://media.xircles.codehaus.org/_projects/groovy/_logos/medium.png 15 | http://groovy.codehaus.org/ 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 62 | 64 | 65 | 66 | 67 | 68 | 69 | 72 | 73 | 77 | 78 | 79 | 80 | 81 | 82 | 84 | 86 | 87 | -------------------------------------------------------------------------------- /src/site/xdoc/about.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | About the project 8 | Tom Nichols 9 | 10 | 11 | 12 |
13 | 14 |

HTTPBuilder was written by Tom Nichols ( 15 | LinkedIn profile). 16 | The code is available under the open-source 17 | ASF 2.0 license. 18 | This allows unrestricted use of the code; modification and distribution in 19 | both commercial and non-commercial products.

20 | 21 |

HTTPBuilder is built on top of a number of excellent open source 22 | products, including Groovy, 23 | Apache HttpClient, 24 | and Json-Lib.

25 | 26 |

If you feel HTTPBuilder has been particularly helpful to you, or would like 27 | special support, please consider a donation. For general support questions, 28 | you can use the 29 | groovy-user mailing list. For more specific questions you can 30 | contact me directly as 31 | well.

32 | 33 |

Thanks and enjoy!

34 | 35 |
36 | 37 | 38 | 40 | 42 |
43 |
44 | 45 |
46 | 50 |
51 | 52 |
-------------------------------------------------------------------------------- /src/test/groovy/groovyx/net/http/AsyncHTTPBuilderTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2003-2008 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * You are receiving this code free of charge, which represents many hours of 17 | * effort from other individuals and corporations. As a responsible member 18 | * of the community, you are asked (but not required) to donate any 19 | * enhancements or improvements back to the community under a similar open 20 | * source license. Thank you. -TMN 21 | */ 22 | package groovyx.net.http 23 | 24 | import org.junit.Ignore 25 | import org.junit.Test 26 | import static groovyx.net.http.ContentType.* 27 | import static groovyx.net.http.Method.* 28 | import java.util.concurrent.ExecutionException 29 | import org.apache.http.conn.ConnectTimeoutException 30 | 31 | /** 32 | * @author tnichols 33 | */ 34 | public class AsyncHTTPBuilderTest { 35 | 36 | @Test public void testAsyncRequests() { 37 | def http = new AsyncHTTPBuilder( poolSize : 4, 38 | uri : 'http://hc.apache.org', 39 | contentType : ContentType.HTML ) 40 | 41 | def done = [] 42 | 43 | done << http.get(path:'/') { resp, html -> 44 | println "${Thread.currentThread().name} response 1" 45 | true 46 | } 47 | 48 | done << http.get(path:'/httpcomponents-client-ga/') { resp, html -> 49 | println "${Thread.currentThread().name} response 2" 50 | true 51 | } 52 | 53 | done << http.get(path:'/httpcomponents-core-dev/') { resp, html -> 54 | println "${Thread.currentThread().name} response 3" 55 | true 56 | } 57 | 58 | done << http.get(uri:'http://svn.apache.org/') { resp, html -> 59 | println "${Thread.currentThread().name} response 4" 60 | true 61 | } 62 | 63 | println done.size() 64 | 65 | def timeout = 30000 66 | def time = 0 67 | while ( true ) { 68 | if ( done.every{ it.done ? it.get() : 0 } ) break 69 | print '.' 70 | Thread.sleep 2000 71 | time += 2000 72 | if ( time > timeout ) assert false : "Timeout waiting for async operations" 73 | } 74 | http.shutdown() 75 | println 'done.' 76 | } 77 | 78 | @Ignore 79 | @Test public void testDefaultConstructor() { 80 | def http = new AsyncHTTPBuilder() 81 | def resp = http.get( uri:'http://ajax.googleapis.com', 82 | path : '/ajax/services/search/web', 83 | query : [ v:'1.0', q: 'Calvin and Hobbes' ], 84 | contentType: JSON ) 85 | 86 | while ( ! resp.done ) Thread.sleep 2000 87 | assert resp.get().size() 88 | assert resp.get().responseData.results 89 | http.shutdown() 90 | } 91 | 92 | @Test public void testPostAndDelete() { 93 | def http = new AsyncHTTPBuilder(uri:'https://api.twitter.com/1.1/statuses/') 94 | 95 | http.auth.oauth System.getProperty('twitter.oauth.consumerKey'), 96 | System.getProperty('twitter.oauth.consumerSecret'), 97 | System.getProperty('twitter.oauth.accessToken'), 98 | System.getProperty('twitter.oauth.secretToken') 99 | 100 | http.client.params.setBooleanParameter 'http.protocol.expect-continue', false 101 | 102 | def msg = "AsyncHTTPBuilder unit test was run on ${new Date()}" 103 | 104 | def resp = http.post(path : 'update.json', 105 | body:[status:msg,source:'httpbuilder1'] ) 106 | 107 | while ( ! resp.done ) Thread.sleep 2000 108 | def postID = resp.get().id 109 | assert postID 110 | 111 | // delete the test message. 112 | resp = http.request( DELETE, JSON ) { req -> 113 | uri.path = "destroy/${postID}.json" 114 | 115 | response.success = { resp2, json -> 116 | assert json.id != null 117 | assert resp2.statusLine.statusCode == 200 118 | println "Test tweet ID ${json.id} was deleted." 119 | return json 120 | } 121 | } 122 | 123 | while ( ! resp.done ) Thread.sleep( 2000 ) 124 | assert resp.get().id == postID 125 | http.shutdown() 126 | } 127 | 128 | 129 | @Test public void testTimeout() { 130 | def http = new AsyncHTTPBuilder( uri:'http://netflix.com', 131 | contentType: HTML, timeout:2 ) // 2ms to force timeout 132 | 133 | assert http.timeout == 2 134 | 135 | def resp = http.get( path : '/ajax/services/search/web', 136 | query : [ v:'1.0', q: 'HTTPBuilder' ] ) 137 | 138 | Thread.sleep 100 139 | try { 140 | resp.get() 141 | assert false 142 | } 143 | catch ( ExecutionException ex ) { 144 | assert ex.cause.getClass() == ConnectTimeoutException 145 | } 146 | } 147 | 148 | @Test public void testPoolsizeAndQueueing() { 149 | def http = new AsyncHTTPBuilder( poolSize : 1 , 150 | uri : 'http://ajax.googleapis.com/ajax/services/search/web' ) 151 | 152 | def responses = [] 153 | /* With one thread in the pool, responses will be sequential but should 154 | * queue up w/o being rejected. */ 155 | responses << http.get( query : [q:'Groovy', v:'1.0'] ) 156 | responses << http.get( query : [q:'Ruby', v:'1.0'] ) 157 | responses << http.get( query : [q:'Scala', v:'1.0'] ) 158 | 159 | def timeout = 60000 160 | def time = 0 161 | while ( true ) { 162 | if ( responses.every{ it.done ? it.get() : 0 } ) break 163 | print '.' 164 | Thread.sleep 2000 165 | time += 2000 166 | if ( time > timeout ) assert false 167 | } 168 | println() 169 | http.shutdown() 170 | } 171 | 172 | @Test public void testInvalidNamedArg() { 173 | try { 174 | def http = new AsyncHTTPBuilder( poolsize : 1 , 175 | uri : 'http://ajax.googleapis.com/ajax/services/search/web' ) 176 | throw new AssertionError("request should have failed due to invalid kwarg.") 177 | } 178 | catch ( IllegalArgumentException ex ) { /* Expected result */ } 179 | } 180 | 181 | } -------------------------------------------------------------------------------- /src/test/groovy/groovyx/net/http/HttpURLClientTest.groovy: -------------------------------------------------------------------------------- 1 | package groovyx.net.http 2 | 3 | import org.junit.Ignore 4 | import org.junit.Test 5 | 6 | import org.apache.http.client.HttpResponseException 7 | import static groovyx.net.http.ContentType.* 8 | import static groovyx.net.http.Method.* 9 | 10 | class HttpURLClientTest { 11 | 12 | def twitter = [ user: System.getProperty('twitter.user'), 13 | consumerKey: System.getProperty('twitter.oauth.consumerKey'), 14 | consumerSecret: System.getProperty('twitter.oauth.consumerSecret'), 15 | accessToken: System.getProperty('twitter.oauth.accessToken'), 16 | secretToken: System.getProperty('twitter.oauth.secretToken') ] 17 | 18 | /** 19 | * This method will parse the content based on the response content-type 20 | */ 21 | @Test public void testGET() { 22 | def http = new HttpURLClient(url:'http://www.google.com') 23 | def resp = http.request( path:'/search', query:[q:'HTTPBuilder'], 24 | headers:['User-Agent':'Firefox'] ) 25 | 26 | println "response status: ${resp.statusLine}" 27 | 28 | def html = resp.data 29 | assert html 30 | assert html.HEAD.size() == 1 31 | assert html.HEAD.TITLE.size() == 1 32 | println "Title: ${html.HEAD.TITLE.text()}" 33 | assert html.BODY.size() == 1 34 | } 35 | 36 | @Test public void testRedirect() { 37 | def http = new HttpURLClient(followRedirects:false) 38 | 39 | def params = [ url:'http://www.google.com/search', 40 | query:[q:'HTTPBuilder', btnI:"I'm Feeling Lucky"], 41 | headers:['User-Agent':'Firefox'] ] 42 | def resp = http.request( params ) 43 | 44 | assert resp.statusLine.statusCode == 302 45 | assert ! http.followRedirects 46 | 47 | http.followRedirects = true 48 | resp = http.request( params ) 49 | assert resp.statusLine.statusCode == 200 50 | assert resp.success 51 | assert resp.data 52 | } 53 | 54 | @Test public void testSetHeaders() { 55 | def http = new HttpURLClient(url:'http://www.google.com') 56 | def val = '1' 57 | def v2 = 'two' 58 | def h3 = 'three' 59 | def h4 = 'four' 60 | http.headers = [one:"v$val", "$v2" : 2] 61 | http.headers.three = 'not Three' 62 | http.headers."$h3" = 'three' 63 | 64 | // def resp = http.request( headers:[one:'v1',two:'2',three:'three',"$h4":"$val"] ) 65 | // private member access to verify the request headers... 66 | /* def headers = resp.@responseBase.@conn.requestProperties 67 | 68 | assert headers.find { it.key == 'one' && it.value[0] == 'v1' } 69 | assert headers.find { it.key == 'two' && it.value[0] == '2' } 70 | assert headers.find { it.key == 'three' && it.value[0] == 'three' } 71 | assert headers.find { it.key == 'four' && it.value[0] == '1' } 72 | */ } 73 | 74 | 75 | @Test public void testFailure() { 76 | def http = new HttpURLClient(url:'http://www.google.com') 77 | 78 | try { 79 | def resp = http.request( path:'/adsasf/kjsslkd' ) 80 | } 81 | catch( HttpResponseException ex ) { 82 | assert ex.statusCode == 404 83 | assert ! ex.response.success 84 | assert ex.response.headers.every { it.name && it.value } 85 | } 86 | assert http.url.toString() == 'http://www.google.com' 87 | } 88 | 89 | /** 90 | * This method is similar to testGET, but it will will parse the content 91 | * based on the given content-type, i.e. TEXT (text/plain). 92 | */ 93 | @Test public void testReader() { 94 | def http = new HttpURLClient() 95 | def resp = http.request( url:'http://validator.w3.org/about.html', 96 | contentType: TEXT, headers: [Accept:'*/*'] ) 97 | 98 | println "response status: ${resp.statusLine}" 99 | 100 | assert resp.data instanceof StringReader 101 | 102 | // we'll validate the reader by passing it to an XmlSlurper manually. 103 | def resolver = ParserRegistry.catalogResolver 104 | def parsedData = new XmlSlurper( entityResolver : resolver ).parse(resp.data) 105 | resp.data.close() 106 | assert parsedData.children().size() > 0 107 | } 108 | 109 | /** W3C pages will have a doctype, but will return a 503 if you do a GET 110 | * for them with the Java User-Agent. 111 | */ 112 | @Test public void testCatalog() { 113 | def http = new HttpURLClient( 114 | url:'http://validator.w3.org/', 115 | contentType: XML ) 116 | 117 | def resp = http.request( path : 'about.html' ) 118 | assert resp.data 119 | } 120 | 121 | /* REST testing with Twitter! 122 | * Tests POST with XML response, and DELETE with a JSON response. 123 | */ 124 | 125 | @Test public void testPOST() { 126 | def http = new HttpURLClient(url:'https://api.twitter.com/1.1/statuses/') 127 | 128 | http.setOAuth twitter.consumerKey, twitter.consumerSecret, 129 | twitter.accessToken, twitter.secretToken 130 | 131 | def msg = "HTTPBuilder unit test was run on ${new Date()}" 132 | 133 | def resp = http.request( method:POST, contentType:JSON, 134 | path:'update.json', timeout: 30000, 135 | requestContentType:URLENC, 136 | body:[status:msg,source:'httpbuilder'] ) 137 | 138 | println "Tweet response status: ${resp.statusLine}" 139 | assert resp.statusLine.statusCode == 200 140 | def json = resp.data 141 | 142 | assert json.text == msg 143 | assert json.user.screen_name == twitter.user 144 | def postID = json.id 145 | 146 | // delete the test message. 147 | resp = http.request( method:DELETE, contentType:JSON, 148 | path : "destroy/${postID}.json" ) 149 | 150 | json = resp.data 151 | assert json.id != null 152 | assert resp.statusLine.statusCode == 200 153 | println "Test tweet ID ${json.id} was deleted." 154 | } 155 | 156 | // @Test 157 | public void testHeadMethod() { 158 | def http = new HttpURLClient(url:'http://api.twitter.com/1/statuses/') 159 | 160 | assert http.url.toString() == "http://api.twitter.com/1/statuses/" 161 | 162 | http.setOAuth twitter.consumerKey, twitter.consumerSecret, 163 | twitter.accessToken, twitter.secretToken 164 | 165 | def resp = http.request( method:HEAD, contentType:"application/xml", 166 | path : 'friends_timeline.xml' ) 167 | 168 | assert resp.headers.Status == "200 OK" 169 | } 170 | 171 | @Test public void testParsers() { 172 | def parsers = new ParserRegistry() 173 | def done = false 174 | parsers.'application/json' = { done = true } 175 | 176 | def http = new HttpURLClient( 177 | url:'https://api.twitter.com/1.1/statuses/', 178 | parsers : parsers ) 179 | 180 | http.setOAuth twitter.consumerKey, twitter.consumerSecret, 181 | twitter.accessToken, twitter.secretToken 182 | 183 | def resp = http.request( contentType: JSON, path : 'home_timeline.json' ) 184 | assert done 185 | assert resp.data 186 | 187 | done = false 188 | parsers.defaultParser = { done = true } 189 | // remove content-type-specific parser to force use of default parser. 190 | parsers.'application/json' = null 191 | resp = http.request( contentType: JSON, path : 'home_timeline.json' ) 192 | assert done 193 | assert resp.data 194 | } 195 | 196 | /* http://googlesystem.blogspot.com/2008/04/google-search-rest-api.html 197 | * http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=Earth%20Day 198 | */ 199 | @Ignore 200 | @Test public void testJSON() { 201 | 202 | def http = new HttpURLClient() 203 | 204 | def resp = http.request( url:'http://ajax.googleapis.com', 205 | method:GET, contentType:JSON , 206 | path : '/ajax/services/search/web', 207 | query : [ v:'1.0', q: 'Calvin and Hobbes' ], 208 | //UA header required to get Google to GZIP response: 209 | headers:['User-Agent' : "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.0.4) Gecko/2008111319 Ubuntu/8.10 (intrepid) Firefox/3.0.4"] ) 210 | 211 | // assert resp.entity.contentEncoding.value == 'gzip' 212 | def json = resp.data 213 | assert json.size() == 3 214 | // println json.responseData 215 | println "Query response: " 216 | json.responseData.results.each { 217 | println " ${it.titleNoFormatting} : ${it.visibleUrl}" 218 | } 219 | } 220 | 221 | @Test public void testUnknownNamedParam() { 222 | def http = new HttpURLClient() 223 | 224 | try { 225 | def resp = http.request( url:'http://ajax.googleapis.com', 226 | method:GET, contentType:JSON , 227 | Path : '/ajax/services/search/web', 228 | query : [ v:'1.0', q: 'Calvin and Hobbes' ] ) 229 | assert false : "Unknown argument should have thrown exception" 230 | } 231 | catch ( IllegalArgumentException ex ) { /* Expected exception */ } 232 | } 233 | 234 | @Test(expected = SocketTimeoutException) 235 | void testTimeout() { 236 | new HttpURLClient(url: 'https://www.google.com/').request(timeout: 1) 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/test/groovy/groovyx/net/http/RESTClientTest.groovy: -------------------------------------------------------------------------------- 1 | package groovyx.net.http 2 | 3 | import org.junit.Ignore 4 | import org.junit.Test 5 | import org.junit.Before 6 | import junit.framework.Assert 7 | import groovy.util.slurpersupport.GPathResult 8 | import org.apache.http.params.HttpConnectionParams 9 | import static groovyx.net.http.ContentType.* 10 | 11 | /** 12 | * @author tnichols 13 | * 14 | */ 15 | public class RESTClientTest { 16 | 17 | def twitter = null 18 | static postID = null 19 | def userID = System.getProperty('twitter.user') 20 | 21 | @Before public void setUp() { 22 | twitter = new RESTClient( 'https://api.twitter.com/1.1/statuses/' ) 23 | twitter.auth.oauth System.getProperty('twitter.oauth.consumerKey'), 24 | System.getProperty('twitter.oauth.consumerSecret'), 25 | System.getProperty('twitter.oauth.accessToken'), 26 | System.getProperty('twitter.oauth.secretToken') 27 | twitter.contentType = ContentType.JSON 28 | HttpConnectionParams.setSoTimeout twitter.client.params, 15000 29 | } 30 | 31 | @Test public void testConstructors() { 32 | twitter = new RESTClient() 33 | assert twitter.contentType == ContentType.ANY 34 | 35 | twitter = new RESTClient( 'http://www.google.com', ContentType.XML ) 36 | assert twitter.contentType == ContentType.XML 37 | } 38 | 39 | @Test public void testHead() { 40 | try { // twitter sends a 302 Found to /statuses, which then returns a 406... What?? 41 | twitter.head path : 'asdf' 42 | assert false : 'Expected exception' 43 | } 44 | // test the exception class: 45 | catch( ex ) { assert ex.response.status == 404 } 46 | 47 | assert twitter.head( path : 'home_timeline.json' ).status == 200 48 | } 49 | 50 | @Test public void testGet() { 51 | // testing w/ content-type other than default: 52 | /* Note also that Twitter doesn't really care about the "Accept" header 53 | anyway, it wants you to put it in the URL, i.e. something.xml or 54 | something.json. But we're still passing the content-type so that 55 | the parser knows how it should _attempt_ to parse the response. */ 56 | def resp = twitter.get( path : 'home_timeline.json' ) 57 | assert resp.status == 200 58 | assert resp.headers.Server == "tfe" 59 | assert resp.headers.Server == resp.headers['Server'].value 60 | assert resp.contentType == JSON.toString() 61 | assert ( resp.data instanceof List ) 62 | assert resp.data.status.size() > 0 63 | } 64 | 65 | @Test public void testPost() { 66 | def msg = "RESTClient unit test was run on ${new Date()}" 67 | 68 | def resp = twitter.post( 69 | path : 'update.json', 70 | body : [ status:msg, source:'httpbuilder' ], 71 | requestContentType : URLENC ) 72 | 73 | assert resp.status == 200 74 | assert resp.headers.Status 75 | assert resp.data.text == msg 76 | assert resp.data.user.screen_name == userID 77 | 78 | RESTClientTest.postID = resp.data.id 79 | println "Updated post; ID: ${postID}" 80 | } 81 | 82 | @Test public void testDelete() { 83 | Thread.sleep 10000 84 | // delete the test message. 85 | if ( ! postID ) throw new IllegalStateException( "No post ID from testPost()" ) 86 | println "Deleting post ID : $postID" 87 | def resp = twitter.delete( path : "destroy/${postID}.json" ) 88 | assert resp.status == 200 89 | assert resp.data.id == postID 90 | println "Test tweet ID ${resp.data.id} was deleted." 91 | } 92 | 93 | @Ignore 94 | @Test public void testOptions() { 95 | // get a message ID then test which ways I can delete it: 96 | def resp = twitter.get( uri: 'http://twitter.com/statuses/user_timeline/httpbuilder.json' ) 97 | 98 | def id = resp.data[0].id 99 | assert id 100 | 101 | // This does not seem to be supported by the Twitter API.. 102 | /* resp = twitter.options( path : "destroy/${id}.json" ) 103 | println "OPTIONS response : ${resp.headers.Allow}" 104 | assert resp.headers.Allow 105 | */ 106 | } 107 | 108 | @Test public void testDefaultHandlers() { 109 | def resp = twitter.get( path : 'user_timeline.json', 110 | query : [screen_name :'httpbuilder',count:2] ) 111 | assert resp.data.size() == 2 112 | 113 | try { 114 | resp = twitter.get([:]) 115 | assert false : "exception should be thrown" 116 | } 117 | catch ( HttpResponseException ex ) { 118 | assert ex.response.status == 404 119 | } 120 | } 121 | 122 | @Test public void testQueryParameters() { 123 | twitter.contentType = 'text/javascript' 124 | twitter.headers = null 125 | def resp = twitter.get( 126 | path : 'user_timeline.json', 127 | queryString : 'count=5&trim_user=1', 128 | query : [screen_name :'httpbuilder'] ) 129 | assert resp.data.size() == 5 130 | } 131 | 132 | @Test public void testUnknownNamedParams() { 133 | try { 134 | twitter.get( Path : 'user_timeline.json', 135 | query : [screen_name :'httpbuilder',count:2] ) 136 | assert false : "exception should be thrown" 137 | } 138 | catch ( IllegalArgumentException ex ) { /* Expected exception */ } 139 | } 140 | 141 | @Test public void testJSONPost() { 142 | def http = new RESTClient("http://restmirror.appspot.com/") 143 | def resp = http.post( 144 | path:'/', contentType:'text/javascript', 145 | body: [name: 'bob', title: 'construction worker'] ) 146 | 147 | println "JSON POST Success: ${resp.statusLine}" 148 | assert resp.data instanceof Map 149 | assert resp.data.name == 'bob' 150 | } 151 | 152 | @Test public void testXMLPost() { 153 | def http = new RESTClient("http://restmirror.appspot.com/") 154 | 155 | def postBody = { 156 | person( name: 'bob', title: 'builder' ) 157 | } 158 | 159 | def resp = http.post( path:'/', contentType: XML, body: postBody ) 160 | 161 | println "XML POST Success: ${resp.statusLine}" 162 | assert resp.data instanceof GPathResult 163 | assert resp.data.name() == 'person' 164 | assert resp.data.@name == 'bob' 165 | assert resp.data.@title == 'builder' 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/test/groovy/groovyx/net/http/RegistryTest.groovy: -------------------------------------------------------------------------------- 1 | package groovyx.net.http 2 | 3 | import org.apache.http.ProtocolVersion 4 | import org.apache.http.entity.StringEntity 5 | import org.apache.http.message.BasicHttpResponse 6 | import org.junit.Test import java.io.StringReader import java.io.ByteArrayInputStream 7 | import static groovyx.net.http.ContentType.* 8 | /** 9 | * @author tnichols 10 | */ 11 | public class RegistryTest { 12 | 13 | @Test public void testParserRegistry() { 14 | def reg = new ParserRegistry(); 15 | 16 | assert reg.defaultParser 17 | reg.each { assert it.key && it.value } 18 | 19 | assert reg."text/plain" 20 | assert reg["text/plain"] 21 | 22 | def newParser = {} 23 | reg."application/json" = newParser 24 | assert reg['application/json'].is( newParser ) 25 | 26 | reg[ ContentType.JSON ] = newParser 27 | assert reg[ "application/javascript" ].is( newParser ) 28 | 29 | reg.defaultParser = newParser 30 | assert newParser.is( reg.defaultParser ) 31 | } 32 | 33 | @Test public void testEncoderAccessors() { 34 | def reg = new EncoderRegistry(); 35 | 36 | reg.each { assert it.key && it.value } 37 | 38 | assert reg."text/plain" 39 | assert reg["text/plain"] 40 | 41 | def newEnc = {} 42 | reg."application/json" = newEnc 43 | reg['application/xml'] = newEnc 44 | assert reg['application/json'].is( newEnc ) 45 | assert reg."application/xml".is( newEnc ) 46 | } 47 | 48 | @Test public void testXMLEncoder() { 49 | def reg = new EncoderRegistry(); 50 | 51 | def entity = reg.encodeXML( { 52 | xml( AAA: 'aaa' ) { 53 | one 'one' 54 | two 'two' 55 | } 56 | }, 'text/xml' ) 57 | 58 | assert entity.contentType.value == "text/xml" 59 | // println entity.content.text 60 | assert entity.content.text == "onetwo" 61 | 62 | entity = reg.encodeXML( 'onetwo',null ) 63 | assert entity.content.text == 'onetwo' 64 | def value = 'something' 65 | entity = reg.encodeXML( "$valuetwo",null ) 66 | assert entity.content.text == "somethingtwo" 67 | } 68 | 69 | @Test public void testCharsetAndText() { 70 | def reg = new EncoderRegistry( charset: "ISO-8859-1" ); 71 | 72 | def entity = reg.encodeText( { out -> 73 | out << "This is a test" 74 | }, null ) 75 | 76 | assert entity.contentType.value == 'text/plain' 77 | assert entity.content.getText('ISO-8859-1') == "This is a test" 78 | assert entity.content.getText('utf-16') != "This is a test" 79 | 80 | def w = { it << "this is a test 1" } as Writable 81 | entity = reg.encodeText( w, 'text/plain' ) 82 | assert entity.content.getText('ISO-8859-1') == "this is a test 1" 83 | 84 | entity = reg.encodeText( "This is a test 2", 'text/notplain' ) 85 | assert entity.contentType.value == 'text/notplain' 86 | assert entity.content.getText('ISO-8859-1') == "This is a test 2" 87 | 88 | entity = reg.encodeText( new StringReader( "This is a test 3\nMore text"), 'text/plain' ) 89 | assert entity.contentType.value == 'text/plain' 90 | assert entity.content.getText('ISO-8859-1') == "This is a test 3\nMore text" 91 | } 92 | 93 | @Test public void testStream() { 94 | def reg = new EncoderRegistry(); 95 | 96 | def data = [ 0x0, 0x1, 0x2 ] as byte[] 97 | 98 | def entity = reg.encodeStream( { out -> // closure 99 | out << data 100 | }, null ) 101 | 102 | assert entity.contentType.value == 'application/octet-stream' 103 | assert entity.contentLength == data.length 104 | def result = new ByteArrayOutputStream() 105 | result << entity.content 106 | assert result.toByteArray() == data 107 | 108 | entity = reg.encodeStream( new ByteArrayInputStream(data), 'application/x-gzip' ) 109 | assert entity.contentLength == data.length 110 | assert entity.contentType.value == 'application/x-gzip' 111 | result = new ByteArrayOutputStream() 112 | result << entity.content 113 | assert result.toByteArray() == data 114 | 115 | entity = reg.encodeStream( data, null ) // byte[] 116 | assert entity.contentLength == data.length 117 | result = new ByteArrayOutputStream() 118 | result << entity.content 119 | assert result.toByteArray() == data 120 | 121 | entity = reg.encodeStream( result, null ) // ByteArrayOutputStream 122 | assert entity.contentLength == data.length 123 | result = new ByteArrayOutputStream() 124 | result << entity.content 125 | assert result.toByteArray() == data 126 | } 127 | 128 | @Test public void testJSONEncoder() { 129 | def reg = new EncoderRegistry(); 130 | 131 | def entity = reg.encodeJSON( [ 132 | first : [ one:1, two:"2" ], 133 | second : 'some string' 134 | ], null ) 135 | 136 | assert entity.contentType.value == "application/json" 137 | // println entity.content.text 138 | assert entity.content.text == '{"first":{"one":1,"two":"2"},"second":"some string"}' 139 | 140 | entity = reg.encodeJSON( ["first", "second", 3, [map:4] ], 'text/javascript' ) 141 | assert entity.contentType.value == "text/javascript" 142 | assert entity.content.text == '["first","second",3,{"map":4}]' 143 | 144 | entity = reg.encodeJSON( { 145 | root { 146 | first { 147 | one = 1 148 | two = '2' 149 | } 150 | second = 'some string' 151 | } 152 | }, null ) 153 | 154 | // println entity.content.text 155 | assert entity.content.text == '{"root":{"first":{"one":1,"two":"2"},"second":"some string"}}' 156 | 157 | entity = reg.encodeJSON( '["first","second",3,{"map":4}]',null ) 158 | assert entity.content.text == '["first","second",3,{"map":4}]' 159 | def another = 'second' 160 | entity = reg.encodeJSON( "['first','$another',3,{'map':4}]",null ) 161 | assert entity.content.text == "['first','second',3,{'map':4}]" 162 | } 163 | 164 | @Test public void testFormEncoder() { 165 | def enc = new EncoderRegistry() 166 | 167 | def param1 = "p1" 168 | def entity = enc.encodeForm( ["${param1}":'one', p2:['two','three']] ) 169 | 170 | assert entity.contentType.elements[0].name == 'application/x-www-form-urlencoded' 171 | assert entity.content.text == "p1=one&p2=two&p2=three" 172 | 173 | entity = enc.encodeForm( "p1=goober&p2=something+else",null ) 174 | assert entity.contentType.value == 'application/x-www-form-urlencoded' 175 | assert entity.content.text == "p1=goober&p2=something+else" 176 | } 177 | 178 | @Test public void testFormParser() { 179 | def parser = new ParserRegistry() 180 | 181 | def entity = new StringEntity( "p1=goober&p2=something+else", "utf-8" ) 182 | // GMOD-137: URLENC parsing doesn't work w/ bad content-type 183 | entity.setContentType "text/plain" // NOT application/x-www-form-urlencoded 184 | 185 | def response = new BasicHttpResponse( new ProtocolVersion( "HTTP", 1, 1 ), 200, "OK" ) 186 | response.entity = entity 187 | def map = parser.parseForm( response ) 188 | assert map 189 | assert map.p1 == 'goober' 190 | assert map.p2 == 'something else' 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/test/groovy/groovyx/net/http/SSLTest.groovy: -------------------------------------------------------------------------------- 1 | package groovyx.net.http 2 | 3 | import static groovyx.net.http.Method.HEAD 4 | 5 | import org.junit.Ignore 6 | import org.junit.Test 7 | import java.security.KeyStore 8 | 9 | import org.apache.http.conn.scheme.Scheme 10 | import org.apache.http.conn.ssl.SSLSocketFactory; 11 | 12 | /** 13 | * @author tnichols 14 | */ 15 | public class SSLTest { 16 | 17 | def uri = "https://dev.java.net/" ; 18 | 19 | @Ignore 20 | @Test public void testTrustedCert() { 21 | def http = new HTTPBuilder( uri ) 22 | 23 | def keyStore = KeyStore.getInstance( KeyStore.defaultType ) 24 | 25 | getClass().getResource( "/truststore.jks" ).withInputStream { 26 | keyStore.load( it, "test1234".toCharArray() ) 27 | } 28 | 29 | final socketFactory = new SSLSocketFactory(keyStore) 30 | socketFactory.hostnameVerifier = SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER 31 | 32 | http.client.connectionManager.schemeRegistry.register( 33 | new Scheme("https", socketFactory, 443) ) 34 | 35 | def status = http.request( HEAD ) { 36 | response.success = { it.statusLine.statusCode } 37 | response.failure = { it.statusLine.statusCode } 38 | } 39 | // dev.java.net doesn't exist anymore, but the server will redirect 40 | // to http://java.net/project/dev which is a 404 page. 41 | // but we still won't get the PeerUnverifiedException which is what 42 | // we're attempting to achieve. 43 | assert status == 404 // 200 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/groovy/groovyx/net/http/ServerTest.groovy: -------------------------------------------------------------------------------- 1 | package groovyx.net.http; 2 | 3 | import static groovyx.net.http.ContentType.* 4 | import static groovyx.net.http.Method.* 5 | 6 | import org.apache.http.params.HttpConnectionParams; 7 | import org.junit.Test; 8 | 9 | public class ServerTest { 10 | @Test 11 | public void testDummy() {} 12 | 13 | // @Test 14 | public void testAlternateParsing() { 15 | 16 | println "-----------TESTING self-server" 17 | def request = new StringBuilder() 18 | def done = false 19 | Thread.start { 20 | println "- Thread running" 21 | def ss 22 | try { 23 | ss = new ServerSocket(11234) 24 | while ( ! done ) { 25 | ss.accept { sock -> 26 | println "- connected" 27 | sock.soTimeout = 10000 28 | sock.withStreams { input, output -> 29 | def reader = new BufferedReader( new InputStreamReader(input)) 30 | def line = reader.readLine() 31 | def entityLength = null 32 | while ( line != '' ) { 33 | // println "+++ $line" 34 | request.append "$line\n" 35 | def contentLengthMatcher = line =~ /(?i)^content-length:\s+(\d+)$/ 36 | if ( contentLengthMatcher.size() ) 37 | entityLength = contentLengthMatcher[0][1] as int 38 | line = reader.readLine() 39 | } 40 | def requestEntity = null 41 | if ( entityLength ) { 42 | requestEntity = new StringBuilder() 43 | (0..entityLength).each { i -> 44 | requestEntity.append reader.read() 45 | } 46 | } 47 | println "- got request: \n$request" 48 | println "- Connected: ${sock.connected} Closed: ${sock.closed}" 49 | output << "HTTP/1.1 200 OK\r\nContent-Type:text/plain\r\n" \ 50 | + "Content-Length:5\r\nConnection: Close\r\n\r\nHello\u0000" 51 | output.flush() 52 | println "- sent response!" 53 | output.close() 54 | } 55 | } 56 | } 57 | println "- Done normally" 58 | } finally { 59 | ss?.close() 60 | println "- Server closed." 61 | } 62 | } 63 | 64 | def http = new HTTPBuilder( 'http://localhost:11234', TEXT ) 65 | http.headers = ['Content-Type':'text/xml',Accept:'text/xml'] 66 | HttpConnectionParams.setSoTimeout( http.client.params, 10000 ) 67 | 68 | println "= Client Sending request..." 69 | def response = http.get( path:'/one/two' ) 70 | println "= Client got response" 71 | done = true 72 | 73 | assert request 74 | assert response 75 | // TODO validate headers 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/test/groovy/groovyx/net/http/URIBuilderTest.groovy: -------------------------------------------------------------------------------- 1 | package groovyx.net.http 2 | 3 | import org.junit.Test 4 | 5 | 6 | /** 7 | * @author tnichols 8 | */ 9 | public class URIBuilderTest { 10 | 11 | @Test public void testConstructors() { 12 | def uri 13 | try { uri = new URIBuilder( null ) } 14 | catch( ex ) { /* Expected exception */ } 15 | 16 | def string = 'http://google.com/search/q?one=1' 17 | uri = new URIBuilder( new URI( string ) ) 18 | assert uri.toString() == string 19 | 20 | uri = new URIBuilder( new URL( string ) ) 21 | assert uri.toString() == string 22 | 23 | uri = new URIBuilder( string ) 24 | assert uri.toString() == string 25 | 26 | def uri2 = URIBuilder.convertToURI( uri ) 27 | assert uri2 == uri.toURI() 28 | } 29 | 30 | @Test public void testCloneAndEquals() { 31 | def string = 'http://google.com/search/q?one=1' 32 | def uri = new URIBuilder( string ) 33 | def u2 = uri.clone() 34 | assert u2 == uri 35 | assert u2.toString() == uri.toString() 36 | } 37 | 38 | @Test public void testTypecast() { 39 | def string = 'http://google.com/search/q?one=1' 40 | def uri = new URIBuilder( string ) 41 | 42 | def u2 = uri as URI 43 | assert ( u2 instanceof URI ) 44 | assert u2.toString() == uri.toString() 45 | 46 | u2 = uri as URL 47 | assert ( u2 instanceof URL ) 48 | assert u2.toString() == uri.toString() 49 | 50 | u2 = uri as String 51 | assert ( u2 instanceof String ) 52 | assert u2 == uri.toString() 53 | 54 | u2 = new URIBuilder( new URI(string) ) 55 | assert u2 == uri 56 | assert u2.toString() == uri.toString() 57 | 58 | u2 = new URIBuilder( new URL(string) ) 59 | assert u2 == uri 60 | assert u2.toString() == uri.toString() 61 | } 62 | 63 | @Test public void testPath() { 64 | def uri = new URIBuilder( 'http://localhost/p1/p2?a=1&b=2&c=3#frag' ) 65 | 66 | uri.path = 'p2a' 67 | assert uri.toString() == 'http://localhost/p1/p2a?a=1&b=2&c=3#frag' 68 | 69 | uri.path = 'p2/p3/' 70 | assert uri.toString() == 'http://localhost/p1/p2/p3/?a=1&b=2&c=3#frag' 71 | 72 | uri.path = 'p4' 73 | assert uri.toString() == 'http://localhost/p1/p2/p3/p4?a=1&b=2&c=3#frag' 74 | 75 | uri.path = '../../p2b' 76 | assert uri.toString() == 'http://localhost/p1/p2b?a=1&b=2&c=3#frag' 77 | 78 | def p = 'p5' 79 | uri.path = "/p4/$p" 80 | assert uri.toString() == 'http://localhost/p4/p5?a=1&b=2&c=3#frag' 81 | } 82 | 83 | @Test public void testPathEscaping() { 84 | def uri = new URIBuilder( 'http://localhost/' ) 85 | uri.path = "/p1 p2" 86 | assert uri.toString() == 'http://localhost/p1%20p2' 87 | 88 | uri.fragment = 'fr#ag' 89 | assert uri.toString() == 'http://localhost/p1%20p2#fr%23ag' 90 | 91 | uri = new URIBuilder( 'http://localhost/p1?one=1#frag' ) 92 | uri.path = "/p1 p2 p3" 93 | assert uri.toString() == 'http://localhost/p1%20p2%20p3?one=1#frag' 94 | } 95 | 96 | // When params are added to a path, does it goober up the escaped-then-unescaped path? 97 | @Test public void testPathEscaping2() { 98 | def uri = new URIBuilder( 'http://johannburkard.de/%22bla%22' ) 99 | uri.query = [ what_is_this: 'i_dont_even' ] 100 | 101 | uri = new URIBuilder( 'http://codehaus.org/') 102 | uri.path = '"bla"' 103 | 104 | uri.fragment = 'what evs' 105 | assert uri.toString() == 'http://codehaus.org/%22bla%22#what%20evs' 106 | uri.query = [ a: 'b#' ] 107 | assert uri.toString() == 'http://codehaus.org/%22bla%22?a=b%23#what%20evs' 108 | uri.port = 80 109 | uri.host = 'google.com' 110 | uri.scheme = 'https' 111 | assert uri.toString() == 'https://google.com:80/%22bla%22?a=b%23#what%20evs' 112 | } 113 | 114 | @Test public void testParams() { 115 | def uri = new URIBuilder( 'http://localhost/p1/p2?a=1&b=2&c=3#frag' ) 116 | assert uri.query.size() == 3 117 | assert uri.query.a == '1' 118 | assert uri.query.b == '2' 119 | assert uri.query.c == '3' 120 | 121 | uri.addQueryParam 'd', '4' 122 | assert uri.query.d == '4' 123 | 124 | assert uri.hasQueryParam( "d" ) 125 | 126 | uri.removeQueryParam "b" 127 | assert ! uri.hasQueryParam( "b" ) 128 | assert uri.query.b == null 129 | uri.addQueryParam 'b', '' 130 | 131 | uri.addQueryParams( [ e : 5, f : 6, a : 7 ] ) 132 | 133 | def query = uri.query 134 | assert query.containsKey( 'b' ) 135 | assert query.size() == 6 136 | assert query == [a:['1','7'], c:'3', d:'4', b:'', e:'5', f:'6'] 137 | 138 | uri.query = [z:0,y:9,x:8] 139 | assert uri.toString() == 'http://localhost/p1/p2?z=0&y=9&x=8#frag' 140 | 141 | uri.addQueryParams( z:1, y:2 ) 142 | assert uri.toString() == 'http://localhost/p1/p2?z=0&y=9&x=8&z=1&y=2#frag' 143 | 144 | uri.addQueryParam 'y', 'blah' 145 | assert uri.toString() == 'http://localhost/p1/p2?z=0&y=9&x=8&z=1&y=2&y=blah#frag' 146 | 147 | // uri.addQueryParam '', 'asdf' 148 | // println uri // prints ...p2?=asdf... but apparently that is a valid URI... 149 | 150 | uri.query = null 151 | assert uri.toString() == 'http://localhost/p1/p2#frag' 152 | 153 | uri.query = [:] 154 | assert uri.toString() == 'http://localhost/p1/p2#frag' 155 | 156 | uri = new URIBuilder( 'http://localhost/p1/p2' ) 157 | uri.addQueryParam 'z','1' 158 | assert uri.query.z == '1' 159 | } 160 | 161 | @Test public void testMostEverythingElse() { 162 | def url = 'http://localhost:80/one/two?a=%261#frag' 163 | def uri = new URIBuilder( url ) 164 | 165 | uri.fragment = 'asdf#2' 166 | assert uri.toString() == 'http://localhost:80/one/two?a=%261#asdf%232' 167 | 168 | uri.path = '/one two' 169 | assert uri.toString() == 'http://localhost:80/one%20two?a=%261#asdf%232' 170 | 171 | uri.scheme = "https" 172 | assert uri.toString() == 'https://localhost:80/one%20two?a=%261#asdf%232' 173 | 174 | uri.port = 8080 175 | assert uri.toString() == 'https://localhost:8080/one%20two?a=%261#asdf%232' 176 | 177 | uri.host = 'google.com' 178 | assert uri.toString() == 'https://google.com:8080/one%20two?a=%261#asdf%232' 179 | 180 | uri.userInfo = 'billy' 181 | assert uri.toString() == 'https://billy@google.com:8080/one%20two?a=%261#asdf%232' 182 | } 183 | 184 | @Test public void testParamEncoding(){ 185 | def uri = new URIBuilder( 'http://localhost:8080#test' ) 186 | 187 | uri.query = [q:'a:b',z:'y&z'] 188 | assert 'http://localhost:8080?q=a%3Ab&z=y%26z#test' == uri.toString() 189 | 190 | uri.scheme = 'ftp' 191 | assert 'ftp://localhost:8080?q=a%3Ab&z=y%26z#test' == uri.toString() 192 | 193 | uri.path = '/one' 194 | assert 'ftp://localhost:8080/one?q=a%3Ab&z=y%26z#test' == uri.toString() 195 | 196 | uri.query = ['a&b':'c+d=e'] 197 | assert "ftp://localhost:8080/one?a%26b=c%2Bd%3De#test" == uri.toString() 198 | 199 | uri.query = [q:"war & peace"] 200 | assert "ftp://localhost:8080/one?q=war+%26+peace#test" == uri.toString() 201 | 202 | uri.host = 'google.com' 203 | assert "ftp://google.com:8080/one?q=war+%26+peace#test" == uri.toString() 204 | 205 | uri.port = 29 206 | assert "ftp://google.com:29/one?q=war+%26+peace#test" == uri.toString() 207 | 208 | uri.fragment = "hi" 209 | assert "ftp://google.com:29/one?q=war+%26+peace#hi" == uri.toString() 210 | } 211 | 212 | @Test public void testProperties() { 213 | // getQuery is already covered; 214 | def uri = new URIBuilder('http://localhost/') 215 | uri.setScheme( 'https' ) 216 | .setUserInfo( 'bill' ) 217 | .setHost( 'www.google.com' ) 218 | .setPort( 88 ) 219 | .setPath( 'test' ) 220 | .setFragment( 'blah' ) 221 | 222 | assert uri.toString() == 'https://bill@www.google.com:88/test#blah' 223 | 224 | assert uri.scheme == 'https' 225 | assert uri.userInfo == 'bill' 226 | assert uri.host == 'www.google.com' 227 | assert uri.port == 88 228 | assert uri.path == '/test' 229 | assert uri.fragment == 'blah' 230 | } 231 | 232 | @Test public void testRawQuery() { 233 | def uri = new URIBuilder('http://localhost:80/') 234 | uri.rawQuery = '1%20AND%202' 235 | assert uri.toString() == 'http://localhost:80/?1%20AND%202' 236 | assert uri.toURI().rawQuery == '1%20AND%202' 237 | uri.path = 'some/path' 238 | assert uri.toString() == 'http://localhost:80/some/path?1%20AND%202' 239 | uri.fragment = 'asdf' 240 | assert uri.toString() == 'http://localhost:80/some/path?1%20AND%202#asdf' 241 | uri.rawQuery = "a%20b=c%31d" 242 | assert uri.toString() == 'http://localhost:80/some/path?a%20b=c%31d#asdf' 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/test/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/test/resources/rss-catalog.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/truststore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgritman/httpbuilder/72fb50a94cbd85f7cde17be0ea6b8e8375bc573e/src/test/resources/truststore.jks --------------------------------------------------------------------------------