google-apibox-apigoogle-http-client

Multipart Request to Box API via Google HTTP Client


I'm trying to call this specific method http://developers.box.com/docs/#files-upload-a-file in Box API with the help of Google HTTP Client library v1.14.1. Currently I see no way of doing this.

If I was using http://hc.apache.org/httpclient-3.x/methods/multipartpost.html, I would add 2 items of StringPart and 1 item of FilePart.

In Google HTTP Client library I see only MultipartContent and Part classes that do not seem to be able to handle pure name/value pairs, as StringPart referenced above.

Here is an excerpt from Apache HTTP Client examples:

HttpPost httppost = new HttpPost("http://localhost:8080" +
                "/servlets-examples/servlet/RequestInfoExample");

FileBody bin = new FileBody(new File(args[0]));
StringBody comment = new StringBody("A binary file of some kind");

MultipartEntity reqEntity = new MultipartEntity();
reqEntity.addPart("bin", bin);
reqEntity.addPart("comment", comment);

httppost.setEntity(reqEntity);

I want to accomplish similar thing, but using Google HTTP Client. Any suggestions would be welcome!


Solution

  • After some investigation I found that I needed Content-Type: multipart/form-data for the Box API and appropriately build the request. It was not possible with the version of Google HTTP Client I was using, so I implemented MultipartFormDataContent class myself and it fits perfectly to the library. Here is the full listing of the class. Maybe it can be included into the library.

    /*
     * Copyright (c) 2013 Google Inc.
     *
     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
     * in compliance with the License. You may obtain a copy of the License at
     *
     * http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software distributed under the License
     * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     * or implied. See the License for the specific language governing permissions and limitations under
     * the License.
     */
    /**
     * This is a modification of com.google.api.client.http.MultipartContent from
     * Google HTTP Client library to support multipart/form-data requests.
     *
     * The original author is Yaniv Inbar.
     */
    public class MultipartFormDataContent extends AbstractHttpContent {
        private static final String NEWLINE = "\r\n";
        private static final String TWO_DASHES = "--";
        private ArrayList<Part> parts = new ArrayList<Part>();
    
        public MultipartFormDataContent() {
            super(new HttpMediaType("multipart/form-data").setParameter("boundary", "__END_OF_PART__"));
        }
    
        @Override
        public void writeTo(OutputStream out) throws IOException {
            Writer writer = new OutputStreamWriter(out, getCharset());
            String boundary = getBoundary();
            for (Part part : parts) {
                HttpHeaders headers = new HttpHeaders().setAcceptEncoding(null);
                if (part.headers != null) {
                    headers.fromHttpHeaders(part.headers);
                }
                headers.setContentEncoding(null)
                       .setUserAgent(null)
                       .setContentType(null)
                       .setContentLength(null);
                // analyze the content
                HttpContent content = part.content;
                StreamingContent streamingContent = null;
                String contentDisposition = String.format("form-data; name=\"%s\"", part.name);
                if (part.filename != null) {
                    headers.setContentType(content.getType());
                    contentDisposition += String.format("; filename=\"%s\"", part.filename);
                }
                headers.set("Content-Disposition", contentDisposition);
                HttpEncoding encoding = part.encoding;
                if (encoding == null) {
                    streamingContent = content;
                } else {
                    headers.setContentEncoding(encoding.getName());
                    streamingContent = new HttpEncodingStreamingContent(content, encoding);
                }
                // write separator
                writer.write(TWO_DASHES);
                writer.write(boundary);
                writer.write(NEWLINE);
                // write headers
                HttpHeaders.serializeHeadersForMultipartRequests(headers, null, null, writer);
                // write content
                if (streamingContent != null) {
                    writer.write(NEWLINE);
                    writer.flush();
                    streamingContent.writeTo(out);
                    writer.write(NEWLINE);
                }
            }
            // write end separator
            writer.write(TWO_DASHES);
            writer.write(boundary);
            writer.write(TWO_DASHES);
            writer.write(NEWLINE);
            writer.flush();
        }
    
        @Override
        public boolean retrySupported() {
            for (Part part : parts) {
                if (!part.content.retrySupported()) {
                    return false;
                }
            }
            return true;
        }
    
        @Override
        public MultipartFormDataContent setMediaType(HttpMediaType mediaType) {
            super.setMediaType(mediaType);
            return this;
        }
    
        /**
         * Adds an HTTP multipart part.
         *
         * <p>
         * Overriding is only supported for the purpose of calling the super
         * implementation and changing the return type, but nothing else.
         * </p>
         */
        public MultipartFormDataContent addPart(Part part) {
            parts.add(Preconditions.checkNotNull(part));
            return this;
        }
    
        /**
         * Sets the boundary string to use.
         *
         * <p>
         * Defaults to {@code "END_OF_PART"}.
         * </p>
         *
         * <p>
         * Overriding is only supported for the purpose of calling the super
         * implementation and changing the return type, but nothing else.
         * </p>
         */
        public MultipartFormDataContent setBoundary(String boundary) {
            getMediaType().setParameter("boundary", Preconditions.checkNotNull(boundary));
            return this;
        }
    
        /**
         * Single part of a multi-part request.
         *
         * <p>
         * Implementation is not thread-safe.
         * </p>
         */
        public static final class Part {
            private String name;
            private String filename;
            private HttpContent content;
            private HttpHeaders headers;
            private HttpEncoding encoding;
    
            public Part setContent(HttpContent content) {
                this.content = content;
                return this;
            }
    
            public Part setHeaders(HttpHeaders headers) {
                this.headers = headers;
                return this;
            }
    
            public Part setEncoding(HttpEncoding encoding) {
                this.encoding = encoding;
                return this;
            }
    
            public Part setName(String name) {
                this.name = name;
                return this;
            }
    
            public Part setFilename(String filename) {
                this.filename = filename;
                return this;
            }
        }
    }