OkHttp Demo

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值