diff --git a/project.properties b/project.properties index fafceaf..3d65818 100644 --- a/project.properties +++ b/project.properties @@ -1,6 +1,6 @@ -#Sat, 21 Sep 2013 02:27:46 -0500 +#Sun, 21 Sep 2014 03:05:17 -0500 name=jdb-util -version=2.2 +version=2.3 lib.local=true -build.number=8 +build.number=9 diff --git a/src/main/com/jdbernard/net/HttpContext.groovy b/src/main/com/jdbernard/net/HttpContext.groovy index fbe3162..34e31ba 100644 --- a/src/main/com/jdbernard/net/HttpContext.groovy +++ b/src/main/com/jdbernard/net/HttpContext.groovy @@ -4,6 +4,10 @@ package com.jdbernard.net import java.net.Socket +import java.net.URLEncoder +import javax.net.ssl.SSLSocket +import javax.net.ssl.SSLSocketFactory +import javax.xml.bind.DatatypeConverter import groovy.json.JsonBuilder import groovy.json.JsonSlurper @@ -15,19 +19,34 @@ public class HttpContext { public String host = 'vbs-test' /** The port number.*/ - public int port = 8000 + public int port = 80 /** A cookie value to send in the request. This value will be automatically * set from any `Set-Cookie` header in the request response. */ public String cookie + /** HTTP Basic Authentication information. If this is a string, it will be + * Base64 encoded as-is. Otherwise it may be an object with "username" and + * "password" properties. The username and password will be concatenated + * with a colon in between. The result will be Base64 encoded. */ + public def basicAuth + + /** Set this to `true` to use HTTPS. Otherwise, HTTP will be used. */ + public boolean secure = false + + /** The default Content-Type that we will send with our requests. This can + * be overridden using the different method overloads. */ + public String defaultContentType = "application/json" + + private SSLSocketFactory sslSocketFactory = null; + /** #### makeMessage * Make and return an HTTP request for the given HTTP method and URL. If * `request` is not null, it is expected to be an object which will be * converted to JSON and included as the request body. The `Host`, * `Cookie`, and `Content-Length` headers will be added based on the * `host` and `cookie` fields and the `request` object. */ - public String makeMessage(String method, String url, def request) { + public String makeMessage(String method, String url, String contentType, def request) { StringBuilder message = new StringBuilder() message.append(method) message.append(" ") @@ -46,24 +65,60 @@ public class HttpContext { message.append(cookie) message.append("\r\n") } + if (basicAuth) { + message.append("Authorization: Basic ") + + if (basicAuth instanceof String) message.append( + DatatypeConverter.printBase64Binary( + (basicAuth as String).bytes)) + + else message.append( + DatatypeConverter.printBase64Binary( + "${basicAuth.username ?: ''}:${basicAuth.password ?: ''}".bytes)) + + message.append("\r\n") } + + if (contentType) { + message.append("Content-Type: ") + message.append(contentType) + message.append("\r\n") } + if (request) { - def jsonRequestBuilder = new JsonBuilder(request) - String requestBody = jsonRequestBuilder.toString() + String requestBody + if (contentType.startsWith("application/json") && + request instanceof Map) { + def jsonRequestBuilder = new JsonBuilder(request) + requestBody = jsonRequestBuilder.toString() } + + else if (contentType.startsWith("application/x-www-form-urlencoded") && + request instanceof Map) + requestBody = urlEncode(request) + + else requestBody = request.toString() message.append("Content-Length: ") message.append(requestBody.length()) message.append("\r\n\r\n") - message.append(requestBody) } + message.append(requestBody) + + message.append("\r\n") } + else message.append("\r\n") - message.append("\r\n") return message.toString() } + /** #### send + * A wrapper around `makeMessage` and `send(String message)` to create a + * request, send it, and return the response. This version allows you to + * specify the request's Content-Type.*/ + public def send(String method, String url, String contentType, def request) { + return send(makeMessage(method, url, contentType, request)) } + /** #### send * A wrapper around `makeMessage` and `send(String message)` to create a * request, send it, and return the response. */ public def send(String method, String url, def request) { - return send(makeMessage(method, url, request)) } + return send(makeMessage(method, url, defaultContentType, request)) } /** #### send * Send a message to the host specified by the object's `host` and `port` @@ -79,7 +134,18 @@ public class HttpContext { */ public def send(String message) { Map result = [headers:[], content: null] - Socket sock = new Socket(host, port) + + Socket sock + + if (secure) { + if (!sslSocketFactory) + sslSocketFactory = SSLSocketFactory.getDefault() + + sock = sslSocketFactory.createSocket(host, port) + } + + else sock = new Socket(host, port) + def startTime sock.withStreams { strin, out -> @@ -164,4 +230,18 @@ public class HttpContext { * `send(String method, String url, def request)` with `method = * "POST"`. */ public def post(String url, def body) { return send('POST', url, body) } + + /** #### post + * A wrapper to perform a `POST` request. This calls + * `send(String method, String url, def request)` with `method = + * "POST"`. This version also allows you to set the request's + * Content-Type. */ + public def post(String url, String contentType, def body) { + return send('POST', url, contentType, body) } + + private String urlEncode(Map m) { + List parts = m.collect { k, v -> + "$k=${URLEncoder.encode(v.toString(), 'UTF-8')}" } + return parts.join("&") } + }