001package jmri.util; 002 003import java.io.BufferedReader; 004import java.io.File; 005import java.io.FileInputStream; 006import java.io.IOException; 007import java.io.InputStreamReader; 008import java.io.OutputStream; 009import java.io.OutputStreamWriter; 010import java.io.PrintWriter; 011import java.net.HttpURLConnection; 012import java.net.URL; 013import java.net.URLConnection; 014import java.util.ArrayList; 015import java.util.List; 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019/** 020 * Sends multi-part HTTP POST requests to a web server 021 * <p> 022 * Based on 023 * http://www.codejava.net/java-se/networking/upload-files-by-sending-multipart-request-programmatically 024 * <hr> 025 * This file is part of JMRI. 026 * <p> 027 * JMRI is free software; you can redistribute it and/or modify it under the 028 * terms of version 2 of the GNU General Public License as published by the Free 029 * Software Foundation. See the "COPYING" file for a copy of this license. 030 * <p> 031 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 032 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 033 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 034 * 035 * @author Matthew Harris Copyright (C) 2014 036 */ 037public class MultipartMessage { 038 039 private final String boundary; 040 private static final String LINE_FEED = "\r\n"; 041 private final HttpURLConnection httpConn; 042 private final String charSet; 043 private final OutputStream outStream; 044 private final PrintWriter writer; 045 046 /** 047 * Constructor initialises a new HTTP POST request with content type set to 048 * 'multipart/form-data'. 049 * <p> 050 * This allows for additional binary data to be uploaded. 051 * 052 * @param requestURL URL to which this request should be sent 053 * @param charSet character set encoding of this message 054 * @throws IOException if {@link OutputStream} cannot be created 055 */ 056 public MultipartMessage(String requestURL, String charSet) throws IOException { 057 this.charSet = charSet; 058 059 // create unique multi-part message boundary 060 boundary = "===" + System.currentTimeMillis() + "==="; 061 URL url = new URL(requestURL); 062 httpConn = (HttpURLConnection) url.openConnection(); 063 httpConn.setUseCaches(false); 064 httpConn.setDoOutput(true); 065 httpConn.setDoInput(true); 066 httpConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); 067 httpConn.setRequestProperty("User-Agent", "JMRI " + jmri.Version.getCanonicalVersion()); 068 outStream = httpConn.getOutputStream(); 069 writer = new PrintWriter(new OutputStreamWriter(outStream, this.charSet), true); 070 } 071 072 /** 073 * Adds form field data to the request 074 * 075 * @param name field name 076 * @param value field value 077 */ 078 public void addFormField(String name, String value) { 079 log.debug("add form field: {}; value: {}", name, value); 080 writer.append("--" + boundary).append(LINE_FEED); 081 writer.append( 082 "Content-Disposition: form-data; name=\"" + name 083 + "\"").append(LINE_FEED); 084 writer.append("Content-Type: text/plain; charset=" + charSet) 085 .append(LINE_FEED); 086 writer.append(LINE_FEED); 087 writer.append(value).append(LINE_FEED); 088 writer.flush(); 089 } 090 091 /** 092 * Adds an upload file section to the request. MIME type of the file is 093 * determined based on the file extension. 094 * 095 * @param fieldName name attribute in form <input name="{fieldName}" 096 * type="file" /> 097 * @param uploadFile file to be uploaded 098 * @throws IOException if problem adding file to request 099 */ 100 public void addFilePart(String fieldName, File uploadFile) throws IOException { 101 addFilePart(fieldName, uploadFile, URLConnection.guessContentTypeFromName(uploadFile.getName())); 102 } 103 104 /** 105 * Adds an upload file section to the request. MIME type of the file is 106 * explicitly set. 107 * 108 * @param fieldName name attribute in form <input name="{fieldName}" 109 * type="file" /> 110 * @param uploadFile file to be uploaded 111 * @param fileType MIME type of file 112 * @throws IOException if problem adding file to request 113 */ 114 public void addFilePart(String fieldName, File uploadFile, String fileType) throws IOException { 115 log.debug("add file field: {}; file: {}; type: {}", fieldName, uploadFile, fileType); 116 String fileName = uploadFile.getName(); 117 writer.append("--" + boundary).append(LINE_FEED); 118 writer.append( 119 "Content-Disposition: form-data; name=\"" + fieldName 120 + "\"; filename=\"" + fileName + "\"") 121 .append(LINE_FEED); 122 writer.append( 123 "Content-Type: " + fileType).append(LINE_FEED); 124 writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED); 125 writer.append(LINE_FEED); 126 writer.flush(); 127 128 try (FileInputStream inStream = new FileInputStream(uploadFile)) { 129 byte[] buffer = new byte[4096]; 130 int bytesRead; 131 while ((bytesRead = inStream.read(buffer)) != -1) { 132 outStream.write(buffer, 0, bytesRead); 133 } 134 outStream.flush(); 135 } 136 137 writer.append(LINE_FEED); 138 writer.flush(); 139 } 140 141 /** 142 * Adds a header field to the request 143 * 144 * @param name name of header field 145 * @param value value of header field 146 */ 147 public void addHeaderField(String name, String value) { 148 log.debug("add header field: {}; value: {}", name, value); 149 writer.append(name + ": " + value).append(LINE_FEED); 150 writer.flush(); 151 } 152 153 /** 154 * Finalise and send MultipartMessage to end-point. 155 * 156 * @return Responses from end-point as a List of Strings 157 * @throws IOException if problem sending MultipartMessage to end-point 158 */ 159 public List<String> finish() throws IOException { 160 List<String> response = new ArrayList<>(); 161 162 writer.append(LINE_FEED).flush(); 163 writer.append("--" + boundary + "--").append(LINE_FEED); 164 writer.close(); 165 166 // check server status code first 167 int status = httpConn.getResponseCode(); 168 if (status == HttpURLConnection.HTTP_OK) { 169 try (BufferedReader reader = new BufferedReader(new InputStreamReader(httpConn.getInputStream()))) { 170 String line; 171 while ((line = reader.readLine()) != null) { 172 response.add(line); 173 } 174 } 175 httpConn.disconnect(); 176 } else { 177 throw new IOException("Server returned non-OK status: " + status); 178 } 179 180 return response; 181 } 182 183 private static final Logger log = LoggerFactory.getLogger(MultipartMessage.class); 184 185}