Android開發中常會用到Post請求發送數據到服務器,有些情況下Post的數據比較大,比如電子市場獲取本地應用信息,然後將應用包名,版本號發送給服務器,應用一多,xml數據就龐大了,10KB~30KB都有可能。這是壓縮Post的數據就很有必要了。
當然,我們用別的消息格式,如protobuf等效率較高的數據格式也能減少發送的數據,但這會增加服務器和客戶端開發人員的工作量,還要花些時間去了解這種數據交換格式。廢話不多說,看看下面Post消息體的格式(包含了一個文件上傳的Post請求)。
-------------------7d4a6d158c9 Content-Disposition: form-data; name="myfile"; filename="test.txt" <this is file content> -------------------7d4a6d158c9 Content-Disposition: form-data; name="text1" foo -------------------7d4a6d158c9 Content-Disposition: form-data; name="text2" <this is gzipped post field> gzipped size:214 original:416 -------------------7d4a6d158c9--
在這個請求體內,name爲text2的字段內容採用了gzip壓縮,模擬較大的字符串字段。當字符串上10KB壓縮量還是挺可觀的 ,可減少一倍到四倍的流量。關於怎麼構造這個消息體可以參考這裏,特別留意這裏
Content-Disposition: form-data; name="text2"
的空格,冒號和分號間分別有空格,不然服務器解析會出錯,至於爲什麼?RFC規定就這麼地。
下面看看服務器端構造Post消息體(這個是在之前一篇文件上傳的文章上增加的功能)
package com.hoot.regx; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; public class PostData { private static final String LONG_STRING = "require 'zlib'equire 'stringio'" + "File.open('t1.gz', 'w') do |f| gz = Zlib::GzipWriter.new(f)" + " gz.write 'part one' gz.closeendFile.open('t2.gz', 'w') do |f|" + " gz = Zlib::GzipWriter.new(f) gz.write 'part 2' gz.close" + "endcontents1 = File.open('t1.gz', \"rb\") {|io| io.read }" + "contents2 = File.open('t2.gz', \"rb\") {|io| io.read }" + "c = contents1 + contents2" + "gz = Zlib::GzipReader.new(StringIO.new(c))" + "gz.each do | l |" + " puts l" + "end"; private static final String CHAR_SET = "UTF-8"; private static final String BOUNDARY = "-----------------7d4a6d158c9"; private static final String TWO_HYPHENS = "--"; private static final String END = "\r\n"; /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { PostData pd = new PostData(); pd.uploadFile(); // pd.uploadXML(); } public void uploadFile() throws IOException { URL url = new URL("http://localhost:4567/upload"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.setRequestMethod("POST"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("Charset", CHAR_SET); conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY); StringBuffer sb = new StringBuffer(); // 分解符 sb.append(TWO_HYPHENS + BOUNDARY + END); // 設置與上次文件相關信息 // 上傳文件信息和文件的內容間必須有一個空行,否則會把後面的數據當做屬性讀 sb.append("Content-Disposition: form-data; name=\"myfile\"; filename=\"test.txt\"" + END + END); System.out.print(sb.toString()); byte[] data = sb.toString().getBytes(); OutputStream os = conn.getOutputStream(); os.write(data); // 一下是文件數據 FileInputStream fis = new FileInputStream(new File("test.txt")); byte[] buf = new byte[1024]; int len = 0; while ((len = fis.read(buf)) > 0) { os.write(buf, 0, len); } System.out.print("<this is file content>"); /** * 注意form-data後面的空格 -----------------------------7d33a816d302b6 * * Content-Disposition: form-data; name="text1" * * foo -----------------------------7d33a816d302b6 */ String split = END + TWO_HYPHENS + BOUNDARY + END + "Content-Disposition: form-data; " + "name=\"text1\" " + END + END + "foo"/* + END */; System.out.print(split); os.write(split.getBytes()); byte[] b = ZipUtil.compress(LONG_STRING.getBytes()); split = END + TWO_HYPHENS + BOUNDARY + END + "Content-Disposition: form-data; " + "name=\"text2\" " + END + END/* + dataStr + END */; System.out.print(split); os.write(split.getBytes()); os.write(b); os.write(END.getBytes()); System.out .print("<this is gzipped post field> gzipped size:" + b.length + " original:" + LONG_STRING.getBytes().length + END); String endStr = TWO_HYPHENS + BOUNDARY + TWO_HYPHENS + END; byte[] end_data = endStr.getBytes(); System.out.print(endStr); os.write(end_data); os.flush(); os.close(); fis.close(); InputStream is = conn.getInputStream(); while ((len = is.read(buf)) > 0) { System.out.write(buf, 0, len); } // is.close(); } }
以下是壓縮字符串的工具類
public static byte[] compress(byte[] data) throws IOException { if (data == null || data.length == 0) { return data; } ByteArrayOutputStream out = new ByteArrayOutputStream(); GZIPOutputStream gzip = new GZIPOutputStream(out); gzip.write(data); gzip.close(); return out.toByteArray(); }
服務器端還是sinatra框架,小巧實用。如果你Servlet順手,那也無所謂,好久沒搞Java EE這套了,忘得差不多了
require 'rubygems' require 'sinatra' require 'haml' require 'zlib' get '/' do 'Hello world' end # Handle GET-request (Show the upload form) get "/upload" do haml :upload end # Handle POST-request (Receive and save the uploaded file) post "/upload" do logger.info "#{params}" unless params[:myfile] && (tmpfile = params[:myfile][:tempfile]) && (name = params[:myfile][:filename]) @error = "No file selected" logger.info "params #{@error} file: #{tmpfile} name: #{name} #{params}" return haml(:error) end directory = 'uploads' path = File.join(directory, name) logger.info "name:#{params[:text1]}, #{params[:text2]}" #gz = Zlib::GzipReader.new(params[:text2]) #print gz.read #gz.close logger.info inflate(params[:text2]) File.open(path, "wb") do |f| f.write(tmpfile.read) end @msg = "file #{name} was successfully uploaded!" end def inflate(string) gz = Zlib::GzipReader.new(StringIO.new(params[:text2].force_encoding("UTF-8"))) data = gz.read end
博文源地址在這裏,PS:沒想到這邊閱讀量這麼高。。。我可憐的VPS上的博客。。。。