In this tutorial, we are going to provide an example of a web services-based application that transfers binary data (e.g. content of an arbitrary file) using base64 encoding/decoding approach. The web services library to be used is JAX-WS (Java API for XML Web Services) which is a built-in technology in Java EE family and is also available in Java SE 6 or later.
In the approach employed by this article, the binary data is embedded directly in the SOAP envelop using base64 text encoding. In other words, the raw binary data is converted to an encoded String which is value of an XML element in the SOAP message. Upon receiving the SOAP message, the receiver decodes the encoded String in order to re-construct the original binary data.
The following picture depicts this process:
This approach is the simplest way and is only suitable for transferring a small amount of binary data. It becomes very inefficient when transferring a large amount of binary data because the base64 text encoding technique bloats the data by a factor of 1.33x (UTF-8 text encoding) or 2.66x (UTF-16 text encoding) of the original size. Also, the encoding/decoding process slows down the application performance.
This approach is also often referred as “by value” or “inline attachment” method. Now, let’s go through an example application that is used to transfer small binary files.
NOTE: To optimize the binary data transfer, read: Using MTOM to optimize binary data transfer with JAX-WS web services.
Let’s define an endpoint interface as follows:
package net.codejava.ws.binary.server; import javax.jws.WebMethod; import javax.jws.WebService; /** * A web service endpoint interface. * @author www.codejava.net * */ @WebService public interface FileTransferer { @WebMethod public void upload(String fileName, byte[] imageBytes); @WebMethod public byte[] download(String fileName); }
This endpoint defines two web methods, upload() and download() which allows the client to upload/download a file by sending/receiving a chunk of bytes.
Write an implementation for the FileTransferer interface as follows:
package net.codejava.ws.binary.server; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import javax.jws.WebMethod; import javax.jws.WebService; import javax.xml.ws.WebServiceException; /** * A web service implementation of an endpoint interface. * @author www.codejava.net * */ @WebService public class FileTransfererImpl implements FileTransferer { @WebMethod public void upload(String fileName, byte[] imageBytes) { String filePath = "e:/Test/Server/Upload/" + fileName; try { FileOutputStream fos = new FileOutputStream(filePath); BufferedOutputStream outputStream = new BufferedOutputStream(fos); outputStream.write(imageBytes); outputStream.close(); System.out.println("Received file: " + filePath); } catch (IOException ex) { System.err.println(ex); throw new WebServiceException(ex); } } @WebMethod public byte[] download(String fileName) { String filePath = "e:/Test/Server/Download/" + fileName; System.out.println("Sending file: " + filePath); try { File file = new File(filePath); FileInputStream fis = new FileInputStream(file); BufferedInputStream inputStream = new BufferedInputStream(fis); byte[] fileBytes = new byte[(int) file.length()]; inputStream.read(fileBytes); inputStream.close(); return fileBytes; } catch (IOException ex) { System.err.println(ex); throw new WebServiceException(ex); } } }
As we can see, the upload() method saves the received bytes array to file specified by the given fileName; and the download() method reads the requested file and returns its content as a bytes array.
Let’s create a server program that publishes the above endpoint implementation as follows:
package net.codejava.ws.binary.server; import javax.xml.ws.Endpoint; /** * A simple web service server. * @author www.codejava.net * */ public class WebServiceServer { public static void main(String[] args) { String bindingURI = "http://localhost:9898/codejava/fileService"; FileTransferer service = new FileTransfererImpl(); Endpoint.publish(bindingURI, service); System.out.println("Server started at: " + bindingURI); } }
Now run this program and we should see the following message in the console.
Server started at: http://localhost:9898/codejava/fileService
The server is now waiting for request. Next, we are going to generate the web service client code.
It’s recommended to use the wsimporttool in order to generate web services client code in Java. Execute the following command at the command prompt:
wsimport -keep -p net.codejava.ws.binary.client http://localhost:9898/codejava/fileService?wsdl
The wsimporttool generates necessary client code and puts .java source files under the package net.codejava.ws.binary.client. Here’s the list of generated files:
NOTES: You must ensure that the sever program is running before executing the wsimportcommand.
Based on the generated web services client code, we can write a client program as follows:
package net.codejava.ws.binary.client; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; /** * A client program that connects to a web service in order to upload * and download files. * @author www.codejava.net * */ public class WebServiceClient { public static void main(String[] args) { // connects to the web service FileTransfererImplService client = new FileTransfererImplService(); FileTransfererImpl service = client.getFileTransfererImplPort(); String fileName = "binary.png"; String filePath = "e:/Test/Client/Upload/" + fileName; File file = new File(filePath); // uploads a file try { FileInputStream fis = new FileInputStream(file); BufferedInputStream inputStream = new BufferedInputStream(fis); byte[] imageBytes = new byte[(int) file.length()]; inputStream.read(imageBytes); service.upload(file.getName(), imageBytes); inputStream.close(); System.out.println("File uploaded: " + filePath); } catch (IOException ex) { System.err.println(ex); } // downloads another file fileName = "camera.png"; filePath = "E:/Test/Client/Download/" + fileName; byte[] fileBytes = service.download(fileName); try { FileOutputStream fos = new FileOutputStream(filePath); BufferedOutputStream outputStream = new BufferedOutputStream(fos); outputStream.write(fileBytes); outputStream.close(); System.out.println("File downloaded: " + filePath); } catch (IOException ex) { System.err.println(ex); } } }
As we can see, this client program connects to the web service then uploads an image file to the service, and finally downloads another image file from the service.
Running the client program we should see the following output:
File uploaded: e:/Test/Client/Upload/binary.png File downloaded: E:/Test/Client/Download/camera.png
And here’s the server’s output:
Received file: e:/Test/Server/Upload/binary.png Sending file: e:/Test/Server/Download/camera.png
Using a monitoring tool such asTCP/IP Monitor in Eclipse IDE, we can spot the SOAP messages delivered along with the request and response as follows:
GET /codejava/fileService?wsdl HTTP/1.1 User-Agent: Java/1.7.0_17 Host: localhost:9898 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: keep-alive POST /codejava/fileService HTTP/1.1 Accept: text/xml, multipart/related Content-Type: text/xml; charset=utf-8 SOAPAction: "http://server.binary.ws.codejava.net/FileTransfererImpl/uploadRequest" User-Agent: JAX-WS RI 2.2.4-b01 Host: localhost:9898 Connection: keep-alive Content-Length: 1971 <?xml version="1.0" ?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Body> <ns2:upload xmlns:ns2="http://server.binary.ws.codejava.net/"> <arg0>binary.png</arg0> <arg1>iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAA....</arg1> </ns2:upload> </S:Body> </S:Envelope>
HTTP/1.1 200 OK Transfer-encoding: chunked Content-type: text/xml;charset=utf-8 Date: Fri, 11 Oct 2013 02:48:45 GMT -- WSDL XML (ommitted for saving space).... HTTP/1.1 200 OK Transfer-encoding: chunked Content-type: text/xml; charset=utf-8 Date: Fri, 11 Oct 2013 02:48:45 GMT <?xml version="1.0" ?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Body> <ns2:uploadResponse xmlns:ns2="http://server.binary.ws.codejava.net/"/> </S:Body> </S:Envelope>
POST /codejava/fileService HTTP/1.1 Accept: text/xml, multipart/related Content-Type: text/xml; charset=utf-8 SOAPAction: "http://server.binary.ws.codejava.net/FileTransfererImpl/downloadRequest" User-Agent: JAX-WS RI 2.2.4-b01 Host: localhost:9898 Connection: keep-alive Content-Length: 218 <?xml version="1.0" ?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Body> <ns2:download xmlns:ns2="http://server.binary.ws.codejava.net/"> <arg0>camera.png</arg0> </ns2:download> </S:Body> </S:Envelope>
HTTP/1.1 200 OK Transfer-encoding: chunked Content-type: text/xml; charset=utf-8 Date: Fri, 11 Oct 2013 02:48:45 GMT <?xml version="1.0" ?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Body> <ns2:downloadResponse xmlns:ns2="http://server.binary.ws.codejava.net/"> <return>iVBORw0KGgoAAAANSUhEUgAAABAAA...</return> </ns2:downloadResponse> </S:Body> </S:Envelope>
CONCLUSION: As we mentioned earlier, this binary data transfer approach is only well-suited for working with small binary data because the base64 text encoding mechanism bloats data size greatly and slows down application performance.