OkHttp Demo

来源:互联网 发布:小学英语四年级的软件 编辑:程序博客网 时间:2024/06/06 06:38

OkHttp可用来发送http请求,做微服务。使用方法很简单,并没有做很多事情,只需要创建Request(包括url,header,body),创建并执行Call,读取ResponseBody中的数据即可。

1.读取Response数据

1.1.String方式读取

private <T> T callAndReadResponseBodyString(Request request, TypeToken<RemoteApiResult<T>> typeToken){        Response response = null;        try {            response = this.getOkHttpClient().newCall(request).execute();            if(!response.isSuccessful())                throw new IOException();        } catch (IOException e) {            return null;        }        RemoteApiResult<T> remoteApiResult = this.gson.fromJson(response.body().charStream(), typeToken.getType());        return remoteApiResult.getData();    }

远程服务方将Java对象转成Json格式返回(Spring MVC 的Controller中return的java对象会自动转),调用方可以通过Gson将Json字符串转换为Java对象。
这种方式很方便,但存在一些问题:
1.Java对象(自己定义)属性中不要有byte,byte[]类型,byte[]类型转成Json再转回byte[]不一定对了;
2.官方文档指出当ResponseBody大于1M时推荐以流的方式读取。

1.2.Stream方式读取

private byte[] callAndReadResponseBodyStream(Request request){        Response response = null;        try {            response = this.getOkHttpClient().newCall(request).execute();            if(!response.isSuccessful())                throw new IOException();        } catch (IOException e) {            return new byte[0];        }        //获取ResponseBody的输入流        InputStream inputStream = response.body().byteStream();        //从输入流读取字节码        byte[] bytes = StreamUtil.getAllBytesOfInputStream(inputStream);        return bytes;    }

2.发送Request

2.1.同步Get请求

/**     * 同步get请求     * @param url     * @return 指定类型的对象     */    public <T> T syncGet(String url, Map<String, String> requestParams, Map<String, String> headers, TypeToken<RemoteApiResult<T>> typeToken){        Request request = new Request.Builder()                .url(this.buildUrlWithRequestParams(url, requestParams))                .headers(this.buildHeaders(headers))                .build();        return this.callAndReadResponseBodyString(request, typeToken);    }

Controller返回一个Java对象即可。

另外,OkHttp中的url是要加上协议的,如http、https等,否则会报错url无效。

2.2.异步Get请求

/**     * 异步get请求     * @param url     */    public <T> void asynGet(String url, Map<String, String> requestParams, Map<String, String> headers, ResponseHandler handler){        Request request = new Request.Builder()                .url(this.buildUrlWithRequestParams(url, requestParams))                .headers(this.buildHeaders(headers))                .build();        this.getOkHttpClient().newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {                if(handler != null)                    handler.handleFailure(call, e);            }            @Override            public void onResponse(Call call, Response response) throws IOException {                if(handler != null)                    handler.handleResponse(call, response);            }        });    }

自己实现Callback接口的两个方法即可,这里通过自己定义的ResponseHandler类来处理。
Controller返回void,数据写到HttpServletResponse的输出流里。如:

@RequestMapping(value = "/get/image/author", method = RequestMethod.GET)    public void getAuthorImage(HttpServletResponse response){        File file = new File("F:\\demo\\okhttp\\okhttp-service\\src\\main\\resources\\static\\author.jpg");        try(FileInputStream fileInputStream = new FileInputStream(file);            OutputStream outputStream = response.getOutputStream()        ) {            byte[] bytes = new byte[(int) file.length()];            fileInputStream.read(bytes);            outputStream.write(bytes);        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }    }

2.3.POST String

public <T> T postString(String url, Map<String, String> requestParams, Map<String, String> headers, String str, MediaType mediaType, TypeToken<RemoteApiResult<T>> typeToken){        Request request = new Request.Builder()                .url(this.buildUrlWithRequestParams(url, requestParams))                .headers(this.buildHeaders(headers))                .post(RequestBody.create(mediaType, str))                .build();        return this.callAndReadResponseBodyString(request, typeToken);    }

RequestBody的数据Controller端有两种方式可以读取:
1.通过HttpServletRequest

@RequestMapping(value = "/reply", method = RequestMethod.POST)    public ApiResult<String> sendMessage(HttpServletRequest request){        String msg;        try {            msg = HttpUtil.readRequestBodyToString(request);        } catch (IOException e) {            msg = null;        }        return new ApiResult<>("Your message: \"" + msg + "\" has been received successfully.");    }
public static String readRequestBodyToString(HttpServletRequest request) throws IOException {        BufferedReader reader = request.getReader();        StringBuilder result = new StringBuilder();        String line;        while((line = reader.readLine()) != null){            result.append(line);        }        return result.toString();    }

2.通过@RequestBody

@RequestMapping(value = "/reply2", method = RequestMethod.POST)    public ApiResult<String> sendMessage(@RequestBody String msg){        return new ApiResult<>("Your message: \"" + msg + "\" has been received successfully.");    }

2.4.POST Stream

public <T> T postStream(String url, Map<String, String> requestParams, Map<String, String> headers, MediaType mediaType, Object object, TypeToken<RemoteApiResult<T>> typeToken){        RequestBody requestFileBody = new RequestBody() {            @Override            public MediaType contentType() {                return mediaType;            }            @Override            public void writeTo(BufferedSink sink) throws IOException {                OutputStream outputStream = sink.outputStream();                String json = gson.toJson(object);                byte[] bytes = json.getBytes("utf-8");                outputStream.write(bytes);            }        };        Request request = new Request.Builder()                .url(this.buildUrlWithRequestParams(url, requestParams))                .headers(this.buildHeaders(headers))                .post(requestFileBody)                .build();        return this.callAndReadResponseBodyString(request, typeToken);    }

感觉就是把Java对象写到流里POST。

Controller可以以byte[]作为参数,数据是可以拿到的,处理方式自己定义即可。

@RequestMapping(value = "/upload", method = RequestMethod.POST)    public ApiResult<String> uploadFile(@RequestBody byte[] bytes) {        String string = new String(bytes);        return new ApiResult<>(string);    }

2.5.POST File

 public <T> T postFile(String url, Map<String, String> requestParams, Map<String, String> headers, MediaType mediaType, File file, TypeToken<RemoteApiResult<T>> typeToken){        Request request = new Request.Builder()                .url(this.buildUrlWithRequestParams(url,requestParams))                .headers(this.buildHeaders(headers))                .post(RequestBody.create(mediaType, file))                .build();        return this.callAndReadResponseBodyString(request, typeToken);    }

Controller也可以用byte[]作为参数,不过显然不能简单的转成String了,除非是文本文件如txt。

2.6.POST Form表单

public <T> T postFormData(String url, Map<String, String> requestParams, Map<String, String> headers, Map<String, String> formData, TypeToken<RemoteApiResult<T>> typeToken){        Request request = new Request.Builder()                .url(this.buildUrlWithRequestParams(url, requestParams))                .headers(buildHeaders(headers))                .post(buildRequestBody(formData))                .build();        return this.callAndReadResponseBodyString(request, typeToken);    }

跟在页面提交form表单是一样的效果,所以Controller的写法同页面提交form表单。

2.7.POST Multipart Request

   public <T> T postMultipartRequest(String url, Map<String, String> requestParams, Map<String, String> headers, List<FormDataPart> formDataParts, TypeToken<RemoteApiResult<T>> typeToken){        MultipartBody.Builder builder = new MultipartBody.Builder();        builder.setType(MultipartBody.FORM);        for(FormDataPart part : formDataParts){            if(part instanceof KeyValueFormDataPart) {                KeyValueFormDataPart keyValueFormDataPart = (KeyValueFormDataPart) part;                builder.addFormDataPart(keyValueFormDataPart.getName(), keyValueFormDataPart.getValue());            }            else {                FileFormDataPart fileFormDataPart = (FileFormDataPart)part;                builder.addFormDataPart(fileFormDataPart.getName(), fileFormDataPart.getFileName(), fileFormDataPart.getRequestBody());            }        }        RequestBody requestBody = builder.build();        Request request = new Request.Builder()                .url(this.buildUrlWithRequestParams(url, requestParams))                .headers(this.buildHeaders(headers))                .post(requestBody)                .build();        return this.callAndReadResponseBodyString(request, typeToken);    }

这里我自己定义了FormDataPart基类和KeyValueFormDataPart、FileFormDataPart两个子类。

这个可以用于带文件上传的form表单,如:

<form action="..." method="post">    <table>        <tr>            <td>用户名</td>            <td><input type="text" name="username"/></td>        </tr>        <tr>            <td>密码</td>            <td><input type="password" name="password"/></td>        </tr>        <tr>            <td>头像</td>            <td><input type="file" name="icon"/></td>        </tr>    </table>    <input type="submit" value="提交"/></form>

Controller的接收数据方式如下:

@RequestMapping(value = "/update/head", method = RequestMethod.POST)    public ApiResult<User> updateHeadIcon(String username, String password, MultipartFile icon){        byte[] bytes;        try {            bytes = icon.getBytes();        } catch (IOException e) {            bytes = null;        }        return userService.updateHeadIcon(username, password, null);    }

file类型的表单项可以用MultipartFile类型作为对应的入参,然后读取字节码即可。

2.8.设置cache、time out,更改OkHttpClient设置
一般,OkHttpClient是事先创建好的单一实例,如:

 private HttpUtil(){        client = new OkHttpClient.Builder()                .connectTimeout(connectTimeout, TimeUnit.SECONDS)                .writeTimeout(writeTimeout, TimeUnit.SECONDS)                .readTimeout(readTimeout, TimeUnit.SECONDS)                .build();    }

创建单一实例类HttpUtil时对OkHttpClient进行一些配置,如time out。

如果在发送请求之前临时想改变OkHttpClient的一些配置,可以这样做:

public OkHttpClient getOkHttpClient(){        if(cacheable){            return this.client.newBuilder().cache(this.cache).build();        } else {            return this.client;        }    }

通过原来的OkHttpClient得到一个Builder来重新build OkHttpClient。例如,可以临时配置Cache。

private Cache cache = new Cache(new File("F:/demo/okhttp/okhttp-client/src/main/resources/cache"), 10 * 1024 *1024);

注意:不是配置了cache就会生效的,需要在服务方返回的Response头里加上cache相关的配置,如

@RequestMapping(value = "/query", method = RequestMethod.GET)    public ApiResult<User> queryUser(HttpServletResponse response, @RequestParam("username") String username){        response.addHeader("Cache-Control", "max-age=3600");        response.addHeader("Last-Modified", Long.toString(System.currentTimeMillis()));        return userService.queryUser(username);    }

3.其它

问题:Last-Modified没有调试成功,不知道问题出在哪。
本文完整代码:https://github.com/JinchaoLv/okhttp-demo

4.参考

OkHttp官方文档:https://github.com/square/okhttp/wiki/Recipes

原创粉丝点击