JavaEE 使用OKhttp和Action进行通信
来源:互联网 发布:手机淘宝端怎么微装修 编辑:程序博客网 时间:2024/04/29 23:18
OKhttp是一个处理网络请求的开源项目,由Square公司开发用于替代HttpUrlConnection和Apache HttpClient(Android API23 6.0中已将HttpClient移除),是一个非常适用于Android(Java)的轻量级框架。
为了在客户端使用OKhttp,本文章还将给出服务器端代码,当然这些代码只是为了测试而编写的简单代码。
发送GET请求
使用OKhttp使发送一个请求变得再简单不过,你只需要提供一个URL和一系列请求参数(对于GET请求,可以直接将请求参数添加到URL尾部)即可。
使用OKhttp发送请求之前,需要创建一个OkHttpClient对象,此对象负责发送请求。建议用一个OkHttpClient对象发送请求,而不是在发送每个请求之前都创建一个新的OkHttpClient对象。
private static OkHttpClient client=new OkHttpClient();
一个请求被封装成一个Request对象,这个Request对象可以包含请求头(Headers)和请求体(RequestBody),这里的GET请求不会用到请求头和请求体,它们将在下文介绍。你可以通过Request.Builder来一步步构建需要的请求(而不是直接new一个Request)。下面这个方法封装了构建一个Request的操作。
private static Request getGetRequest(String url, String[] params) { if(params!=null && params.length>0) { if(params.length%2!=0) throw new IllegalArgumentException("The number of request parameters must be even!"); StringBuilder sb=new StringBuilder(url); for(int i=0; i<params.length-1; i+=2) { sb.append((i>0) ? "&" : "?"). append(params[i]). append("="). append(params[i+1]); } url=sb.toString(); } return new Request.Builder().url(url).build(); }
构建好一个请求后,就可以向服务器发起一个对话(Call)了。OkHttpClient负责发起一个新的Call,这个Call会返回一个响应(Response)。注意,这里使用了同步的方式发起一个Call,最好在一个子线程中执行。
public static void get(String url, String[] params, Callback callback) { Request request=getGetRequest(url, params); Call call=client.newCall(request); try { Response response=call.execute(); callback.onResponse(call, response); } catch (IOException e) { callback.onFailure(call, e); } }
另外也可以异步提交一个请求,不过此时不会立即返回一个Response,而是在之后的某个时间点回调Callback对象的onResponse(收到响应)或onFailure(发生异常)方法。
call.enqueue(callback);
现在我们向服务器发送一个GET请求,并提交用户名和密码(user和password),下面是测试代码:
HttpUtils.get( "http://localhost:8080/HttpServer/zzw/get", new String[]{"user", "张三", "password", "123456"}, new Callback(){ @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { System.out.println(response.body().string()); } });
我们在onResponse方法中简单显示了来自服务器的响应。和Request类似,Reponse也可以有响应头(Headers)和响应体(ResponseBody),响应报文就封装在响应体中。相应的服务端代码如下(如果你不感兴趣可以直接跳过哦):
public class GetAction extends ActionSupport { private static final long serialVersionUID = -7442519273670774235L; private String user; private String password; public void setUser(String u) { user=u; } public void setPassword(String p) { password=p; } public String getUser() { return user; } public String getPassword() { return password; } @Override @Action(value="/zzw/get", results={@Result(name="success", location="user.jsp")}) public String execute() throws Exception { return SUCCESS; }}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><%@ page language="java" contentType="text/html; charset=GBK" pageEncoding="UTF-8"%><%@ taglib uri="/struts-tags" prefix="s"%><meta http-equiv="Content-Type" content="text/html; charset=GBK"><title>get page</title></head><body> 用户: <s:property value="user"/><br> 密码: <s:property value="password"/></body></html>
这里的Action使用了Convention插件进行了配置,使其可以接收任何发给http://localhost:8080/HttpServer/zzw/get
的请求。
发送POST请求
发送POST请求比GET请求稍微复杂点,不过大体流程还是一致的,先看代码:
private static Request getPostRequest(String url, String[] params) { FormBody.Builder builder=new FormBody.Builder(); if(params!=null && params.length>0) { if(params.length%2!=0) throw new IllegalArgumentException("The number of request parameters must be even!"); for(int i=0; i<params.length-1; i+=2) builder.add(params[i], params[i+1]); } RequestBody requestBody=builder.build(); return new Request.Builder().url(url).post(requestBody).build(); }
由于POST请求的请求参数都是放在报文体中的,因此需要在”build”过程中添加进请求体中(相比于GET请求更安全)。另外还需要调用Builder的post方法指明发送的是一个POST请求。
注意,这里是按照“提交表单”的方式发送一个POST请求,也就是说这里的POST请求和方法为post的form表单一样。除了这种方式之外,还有一种用于上传文件的POST请求将在后面介绍。
public static void post(String url, String[] params, Callback callback) { Request request=getPostRequest(url, params); Call call=client.newCall(request); try { Response response=call.execute(); callback.onResponse(call, response); } catch (IOException e) { callback.onFailure(call, e); } }
客户端测试代码如下:
String user=null;try { user=URLEncoder.encode("张三", "GBK");} catch (UnsupportedEncodingException e) { e.printStackTrace();}HttpUtils.post( "http://localhost:8080/HttpServer/zzw/form_post", new String[]{"user", user, "password", "123456"}, new Callback(){ @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { System.out.println(response.body().string()); } });
相应的服务端代码如下:
public class FormPostAction extends ActionSupport { private static final long serialVersionUID = -2701435552091943728L; private String user; private String password; private String encoding; public void setUser(String u) { user=u; } public void setPassword(String p) { password=p; } public void setEncoding(String e) { encoding=e; } public String getUser() { return user; } public String getPassword() { return password; } public String getEncoding() { return encoding; } @Override @Action(value="/zzw/form_post", params={"encoding", "GBK"}, results={@Result(name="success", location="user.jsp")}) public String execute() throws Exception { user=URLDecoder.decode(user, encoding); return SUCCESS; }}
注意到,我们在客户端对提交的字符串进行了编码,而在服务端对接收的字符串进行了解码。这是因为需要保证前端和后端编码一致才不会出现乱码,通常客户端的编码是在jsp、HTML等页面中设置的。
上传文件
当客户端需要向服务端上传文件时,比如在课程网站上提交作业时(让我又想起要在Word里面打各种奇奇怪怪的数学符号),这时再使用上面介绍的POST请求就不行了。
我们先来看个上传文件的例子,先是jsp页面代码:
<form method="post" action="upload" enctype="multipart/form-data"> 选择文件: <input type="file" id="file" name="file"><br> <input type="submit" value="上传"><br></form>
这张图片是上传某个文件(图片)时抓获的Request和Response信息。
在Request Headers中有一个Content-Type字段,可以看到这个字段的值包含multipart/form-data
,和form表单的enctype属性值相同;另外boundary指定了多个文件(参数)之间的边界。
再看Request Payload,因为我们只上传了一个文件,所以这里只有两个boundary字符串。重点在于这里有一个Content-Disposition字段,这是我们真正需要的,里面的”name=’file’”和form表单中类型为file的input名字一致。
关于上面的Request Headers和Request Payload,详细的分析可以参考鸿洋大神的文章《从原理角度解析Android (Java) http 文件上传》。
下面是真正用于上传文件的POST请求代码:
private static Request getPostRequest(String url, Part[] parts) { MultipartBody.Builder builder=new MultipartBody.Builder(). setType(MultipartBody.FORM); if(parts!=null && parts.length>0) { for(int i=0; i<parts.length; i++) builder.addPart(parts[i].getHeaders(), parts[i].getRequestBody()); } RequestBody requestBody=builder.build(); return new Request.Builder().url(url).post(requestBody).build(); }
上传文件的请求体是MultipartBody而不是FormBody,在MultipartBody中可以添加需要上传的文件和需要提交的请求参数,不管是上传文件还是提交请求参数,都需要提供一个请求头和请求体。
我们提供了一个Part接口来提供请求头和请求体:
public interface Part { Headers getHeaders(); RequestBody getRequestBody(); }
同时我们也提交了实现Part接口的文件上传类(FilePart)和参数提交类(StringPart):
public static class FilePart implements Part { private File file; private MediaType mediaType; private String formName; public FilePart(File f, String mt, String fn) { file=f; mediaType=MediaType.parse(mt); formName=fn; } @Override public Headers getHeaders() { return Headers.of("Content-Disposition", "form-data; name=\""+formName+ "\"; filename=\""+file.getName()+"\""); } @Override public RequestBody getRequestBody() { return RequestBody.create(mediaType, file); } } public static class StringPart implements Part { private String param; private String formName; public StringPart(String p, String fn) { param=p; formName=fn; } @Override public Headers getHeaders() { return Headers.of("Content-Disposition", "form-data; name=\""+formName+"\""); } @Override public RequestBody getRequestBody() { return RequestBody.create(null, param); } }
可以看到,如果想要上传文件,需要在请求头中添加form表单中类型为file的input名字和文件名,还要在请求体中添加文件的media type和File对象。
如果想要提交请求,需要在请求头中添加form表单中类型为text的input名字(对应与这里的StringPart,其实不一定是text类型),还要在请求体中添加请求参数。
下面我们进一步封装此POST请求:
public static void post(String url, Part[] parts, Callback callback) { Request request=getPostRequest(url, parts); Call call=client.newCall(request); try { Response response=call.execute(); callback.onResponse(call, response); } catch (IOException e) { callback.onFailure(call, e); } }
好了,现在你只需要提供URL、上传内容和回调对象就行了。实际上,大多数情况下都会上传文件和提交字符串形式的请求参数,因此你可以使用我们提供的FilePart和StringPart,现在你只需要提供URL、文件和文件的media type(或者请求参数)以及回调对象即可。
现在我们马上来测试一下:
HttpUtils.post( "http://localhost:8080/HttpServer/zzw/upload", new Part[]{new FilePart(new File("res\\picture.jpg"), "image/jpg", "src")}, new Callback(){ @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { System.out.println(response.body().string()); } });
相应的服务端代码如下:
public class UploadAction extends ActionSupport { private static final long serialVersionUID = -7408370789285125065L; private File src; private String srcContentType; private String srcFileName; private String destDir; private String destFileName; public void setSrc(File f) { src=f; } public void setSrcContentType(String ct) { srcContentType=ct; } public void setSrcFileName(String fn) { srcFileName=fn; } public void setDestDir(String d) { destDir=d; } public File getSrc() { return src; } public String getSrcContentType() { return srcContentType; } public String getSrcFileName() { return srcFileName; } public String getDestDir() { return destDir; } public String getDestFileName() { return destFileName; } @Action(value="/zzw/upload", params={"destDir", "F:\\workspace\\HttpServer\\res\\dest\\"}, results={@Result(name="success", location="upload.jsp")}) public String upload() throws Exception { destFileName=UUID.randomUUID().toString().replaceAll("-", "")+ srcFileName.substring(srcFileName.indexOf('.')); File dest=new File(destDir+destFileName); FileUtils.copyFile(src, dest); return SUCCESS; }}
上传成功后可以在%SERVER_PROJECT%/res/dest/
目录下找到客户端上传的文件。
下载文件
相信大多数人下载文件的次数都远远超过上传文件的次数,我们上面已经介绍了如何上传文件,那么你可能已经迫不及待地想要知道如何下载文件了。别急,下载文件的方法其实我们已经写好了,只需要稍微修改一下回调方法就OK了。
这里我们先来看看服务端到底是怎样相应一个下载文件的请求的:
public class DownloadAction extends ActionSupport { private static final long serialVersionUID = 9078680027961237229L; private String srcPath; public String getSrcPath() { return srcPath; } public void setSrcPath(String path) { srcPath=path; } public InputStream getTarget() throws Exception { return new FileInputStream(srcPath); }}
此Action在struts.xml中的配置如下:
<package name="zzw" extends="struts-default" namespace="/zzw"> <action name="download" class="com.zzw.action.DownloadAction"> <param name="srcPath">F:\workspace\HttpServer\res\src\butterfly.jpg</param> <result name="success" type="stream"> <param name="contentType">image/jpg</param> <param name="inputName">target</param> <param name="contentDisposition">filename="butterfly.jpg"</param> <param name="bufferSize">4096</param> </result> </action> </package>
可以看到,服务端将返回一个类型为”image/jpg”的图像文件的字节流。我们只需要在客户端接收这个字节流并将其写入一个(图像)文件即可。
那么,你可能会问,到底是发送GET请求好呢,还是发送POST请求好呢?经测试后发现,不管是发送GET请求还是POST请求,都可以成功下载文件,不过发送GET请求更为简单。
下面是测试代码:
HttpUtils.get( "http://localhost:8080/HttpServer/zzw/download", null, new Callback(){ @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { String fileName=response.header("Content-Disposition"); int end=fileName.lastIndexOf('"'); int begin=fileName.lastIndexOf('"', end-1)+1; fileName=fileName.substring(begin, end); OutputStream os=new FileOutputStream("res\\"+fileName); os.write(response.body().bytes()); os.flush(); os.close(); System.out.println("download completed!"); } });
注意,这里返回的Content-Disposition是filename=”butterfly.jpg”,所以需要从中提取文件名。
下载完成后可以在%CLIENT_PROJECT%/res/
目录下找到下载的文件。
使用json进行通信
在网络通信中,json格式的文本是最常用的一种。既然如此,我们当然也想使用json格式的文本进行通信了。其实,json格式的文本说白了就是按照某个特定的格式编写的文本(字符串),我们只需要对其进行解析就行了(正则表达式)。
说到这,如果你真的自己去写json格式文本的解析器,那我也只能。。。Google提供了一个开源工具GSON,它使得解析json格式的文本变得像操作一个对象一样简单。
下面有一段json格式的文本:
{"name":"张三","age":22}[{"name":"李四","age":23},{"name":"王五","age":24}]
第一行是一个人,第二行是两个人的集合。我们定义一个Person类来表示这样一个人:
public class Person { private String name; private int age; public Person(String n, int a) { name=n; age=a; } public void setName(String n) { name=n; } public void setAge(int a) { age=a; } public String getName() { return name; } public int getAge() { return age; } @Override public String toString() { return "{name:"+name+",age:"+age+"}"; }}
下面来看我们的测试代码:
HttpUtils.get("http://localhost:8080/HttpServer/zzw/json", null, new Callback(){ @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { String msg=URLDecoder.decode(response.body().string(), "GBK"); String[] msgs=msg.split("\r\n"); System.out.println("response body="+msg); Gson gson=new Gson(); Person person=gson.fromJson(msgs[0], Person.class); System.out.println("person="+person); List<Person> persons=gson.fromJson(msgs[1], new TypeToken<List<Person>>(){}.getType()); System.out.println("persons="+persons); }});
相应的服务端代码如下:
public class JsonAction extends ActionSupport { private static final long serialVersionUID = 1059643779198325028L; @Action(value="/zzw/json", results={@Result(name="success", location="json.jsp")}) public String execute() throws Exception { HttpServletRequest request=ServletActionContext.getRequest(); request.setCharacterEncoding("GBK"); request.getSession(true); HttpServletResponse response=ServletActionContext.getResponse(); response.setCharacterEncoding("GBK"); PrintWriter writer=response.getWriter(); Gson gson=new Gson(); Person person=new Person("张三", 22); String single=URLEncoder.encode(gson.toJson(person), "GBK"); List<Person> persons=new ArrayList<>(); persons.add(new Person("李四", 23)); persons.add(new Person("王五", 24)); String list=URLEncoder.encode(gson.toJson(persons), "GBK"); writer.write(single+URLEncoder.encode("\r\n", "GBK")+list); writer.flush(); writer.close(); return SUCCESS; }}
可以看到,我们使用GSON解析这个json格式的文本只用了四行代码!
使用GSON时,需要先创建一个GSON对象"Gson gson=new Gson();"
,如果想把一个对象转换成json字符串,只需调用toJson方法;如果想把一个json字符串转换成对象,只需调用fromJson方法。
另外,注意解析一个对象和解析一个对象集合为json字符串需要传递不同的参数。解析对象只需要对象类型,而解析对象集合需要借助TypeToken类。
源代码
上述所有代码(包括客户端和服务端)都已上传到GitHub:
https://github.com/jzyhywxz/OKhttpTest
- JavaEE 使用OKhttp和Action进行通信
- JavaEE 使用OKhttp和Action进行通信
- 最全面的Android和JavaEE项目(S2SH)使用WebService进行相互通信的讲解(JavaEE篇)
- 最全面的Android和JavaEE项目(S2SH)使用WebService进行相互通信的讲解(Android篇)
- 使用HttpURLConnection和使用OkHttp来进行网络访问
- 使用GitHub和Eclipse进行javaEE开发步骤
- OKHttp通信使用(一)
- OKHttp通信使用(二)
- javaEE 使用JDBC进行批处理
- 使用OKHttp进行网络访问
- 使用Intent和IntentFilter进行通信
- 使用EventBus进行Fragment和Activity通信
- 使用Intent和IntentFilter进行通信
- 使用EventBus进行Fragment和Activity通信
- 使用Intent和IntentFilter进行通信
- Activity和Service 使用Binder进行通信
- 使用Intent和IntentFilter进行通信
- OKHTTP通信使用(三)HTTPS
- Cerebro 一个跨平台的桌面快捷使用工具
- Today一只菜鸡的PAT甲级测试(PAT1124, PAT1125, PAT1126, PAT1127)
- Sleeping会话导致阻塞原理(下)
- 最长公共子序列
- EditText 相关
- JavaEE 使用OKhttp和Action进行通信
- 爱和自由
- Java reflect 你会了吗?
- CentOS安装Jenkins全程并启动一个Maven工程
- 移动硬盘“文件或目录损坏且无法读取”的解决办法(转)
- 基于当前根文件系统的轻量级chroot jail
- Java中线程的生命周期
- 编写一个将输入复制到输出的程序,并将其中连续的多个空格用一个空格代替。
- 最优编辑练习