There have been numerous articles on Retrofit2 on the Web, but they tend to be more descriptive than practical, partly because the free apis on the Web have limited data formats and access modes (usually Get mode only). Another is that not every developer can write a server-side interface. As a result, the effects of certain parameters were not intuitive when learning Retrofit2, so I tried to build a local server using Nodejs, which provided several interfaces for Get and Post mode access calls, file upload and file download functions. The returned data is formatted as Json objects and Json arrays, and the required parameter formats can be customized by the user

This article won’t go too far into the use of Retrofit2, but will focus on building a server-side interface and how Retrofit2 interacts with the server

First, the server

The server interface uses Nodejs, the IDE is WebStorm, Nodejs version is 10.2.0

Open WebStorm, select New Project, selectNode.js Express AppTo create a project

The upload folder is used to store the files sent from the client. The resultjson. js file is used to unify the data format returned by the server. The api.js file is used to hold the written interface and start the server, and it is the api.js file that we need to focus on

1.1, resultJson. Js

Here is the resultjson. js file, which contains all the code shown below

/** * if a normal result is returned *@param res
 * @param data* /
exports.onSuccess = function (res, data) {
    var result = {};
    result.code = 1;
    result.msg = 'success';
    result.data = data;
    res.json(result);
};

/** * when an error occurs *@param res
 * @param code
 * @param msg* /
exports.onError = function (res, code, msg) {
    var error = {};
    error.code = code;
    error.msg = msg;
    res.json(error);
};

/** * No data is recorded *@param res* /
exports.onNoRecord = function (res) {
    exports.onError(res, 1000.'No record');
};

/** * Parameter error *@param res* /
exports.onParamsError = function (res) {
    exports.onError(res, 1001.'Parameter error');
};

/** * System error *@param res* /
exports.onSystemError = function (res) {
    exports.onError(res, 1002.'System error');
};
Copy the code

Resultjson. js encapsulates all possible results of network requests and unify the data format returned by the server. When a normal result is returned, the onSuccess method is called, and the returned data format is similar to the following. The return code is fixed as “1”, and the returned message MSG is fixed as “success”, and data contains the actual data to be returned

{"code":1."msg":"success"."data": {"name":"leavesC"."mobile":123456}}
Copy the code

When the argument passed to the server is wrong, the onParamsError method is called and the data returned is in the format shown below

{"code":1001."msg":"Parameter error"}
Copy the code

Other abnormal cases return data in the same format, containing only different return codes and return information values

1.2, API. Js

The api.js file contains all the interfaces. Here is a Get interface, and others will be introduced as they are used

In this case, the require function is used to load the required module, similar to loading the required dependent libraries in Java. App. The get () show that the interface support is get pattern request, access interface path suffix is: “/ get/get string”, the complete access path is: http://localhost:1995/Get/getString

The req parameter contains the request parameters brought by the client, the res parameter is used to write the data to be returned to the client, and app.listen(1995) is used to start the server and specify port 1995 for listening

When the client accesses the interface, the interface prints out all the request parameters and headers brought by the client, as well as the actual generated access links

This completes a simple Get interface

// the require function is used to load the required module
var express = require('express');
var bodyParser = require('body-parser');
var multiparty = require('multiparty');
var resultJson = require('.. /routes/resultJson');
var app = express();
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());

app.get('/Get/getString'.function (req, res) {
    // Request parameters
    var query = req.query;
    for (var key in query) {
        console.log("Key is:", key, " , value is: ", query[key]);
    }
    / / request header
    var headers = req.headers;
    for (var key in headers) {
        console.log(Header key is:, key, " , value is: ", headers[key]);
    }
    / / links
    console.log("Url:", req.url);

    // If the request header has a key value of "userName", if the value is not "leavesC", the request parameter is considered wrong
    // If there is no request header with key value "userName", the request is not affected
    // Note that the key value in the request header is set to lowercase
    var userName = headers['username'];
    if(userName && userName ! = ='leavesC') {
        return resultJson.onParamsError(res);
    }
    var data = {};
    data.name = 'leavesC';
    data.mobile = 123456; resultJson.onSuccess(res, data); }); ...// Start the server and listen on the specified port 1995
app.listen(1995);
Copy the code

Second, the client

The IDE used by the client is IntelliJ IDEA. Gradle is used to build the project, which is basically consistent with Android Studio

Introduced support for Retrofit2 and Converter-Gson

    implementation 'com. Squareup. Retrofit2: retrofit: 2.4.0'
    implementation 'com. Squareup. Retrofit2: converter - gson: 2.4.0'
Copy the code

A Get request

Since I set up the server locally, the baseUrl used to build Retrofit should point to a local IP address

/ * * * the author: chenZY * time: 2018/5/29 18:53 * description: https://github.com/leavesC * /
public class HttpConfig {

    public static final String BASE_URL = "http://localhost:1995/";

}
Copy the code

The new GetService interface is used to declare the methods to access the Get interface. Each method contains different parameter values. You can easily distinguish the different methods according to the log information printed by the server

/ * * * the author: chenZY * time: 2018/5/29 18:54 * description: https://github.com/leavesC * /
public interface GetService {

    // Get request with no parameters
    @GET("Get/getString")
    Call<ResponseBody> getNormal(a);

    // Get request with request parameters
    @GET("Get/getString")
    Call<ResponseBody> getWithQuery(@Query("name") String name, @Query("age") int age);

    // Get request with request parameters
    @GET("Get/getString")
    Call<ResponseBody> getWithMap(@QueryMap Map<String, String> map);

    // Get requests with request parameters and fixed headers
    @GET("Get/getString")
    @Headers({"userName:leavesC"})
    Call<ResponseBody> getWithQueryAndHeaders(@Query("name") String name, @Query("age") int age);

    // Get requests with variable request parameters and headers
    @GET("Get/getString")
    Call<ResponseBody> getWithQueryAndHeader(@Header("userName") String userName, @Query("name") String name, @Query("age") int age);

    // A Get request with the request value as part of the link
    @GET("Get/getString/{id}")
    Call<ResponseBody> getWithPath(@Path("id") int id);

    // Get requests with the request value as part of the link, and use Gson Converter
    @GET("Get/getUser/{startId}/{number}")
    Call<ListResponse<User>> getWithGsonConverter(@Path("startId") int startId, @Path("number") int number);

}
Copy the code

2.1. Without any parameters

Here’s a look at the request without any custom parameters and headers

//Get requests do not take any custom parameters or headers. The access link is: /Get/getString
    private static void getNormal(a) {
        GetService getService = buildRetrofit().create(GetService.class);
        getService.getNormal().enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.isSuccessful()) {
                    try {
                        / / return data: {" code ": 1," MSG ":" success ", "data" : {" name ":" leavesC ", "mobile" : 123456}}
                        System.out.println("onResponse body: " + response.body().string());
                    } catch(IOException e) { e.printStackTrace(); }}else {
                    System.out.println("onResponse code: "+ response.code()); }}@Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                System.out.println("onFailure: "+ t.getMessage()); }}); }Copy the code

Log information displayed on the server is as follows

Header information Key is: host, value is: localhost:1995Header information Key is: connection, value is: keep-alive header information Key is: accept-encoding, value is: gzip header information Key is: user-agent , value is: okhttp/3.10. 0Url: / Get/Get stringCopy the code

The data obtained by the client is shown below

{"code":1."msg":"success"."data": {"name":"leavesC"."mobile":123456}}
Copy the code

2.2. Bring the request parameters

If you annotate the request method with @query and the corresponding request parameter, the request parameter will be the suffix of the access link

	//Get requests will take the request parameters, which will be the suffix of the link, resulting in the link: /Get/getString? name=leavesC&age=24
    private static void getWithQuery(a) {
        GetService getService = buildRetrofit().create(GetService.class);
        getService.getWithQuery("leavesC".24).enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.isSuccessful()) {
                    try {
                        / / the data returned is: {" code ": 1," MSG ":" success ", "data" : {" name ":" leavesC ", "mobile" : 123456}}
                        System.out.println("onResponse body: " + response.body().string());
                    } catch(IOException e) { e.printStackTrace(); }}else {
                    System.out.println("onResponse code: "+ response.code()); }}@Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                System.out.println("onFailure: "+ t.getMessage()); }}); }Copy the code

Log information displayed on the server is as follows

Parameter Key is: name, value is: leavesC Parameter Key is: age, value is: 24 Header information Key is: host, value is: Localhost :1995 Header information Key is: connection, value is: keep-alive Header information Key is: accept-encoding, value is: Gzip header information Key is: user-agent, value is: okhttp/3.10.0 Url: /Get/getString? name=leavesC&age=24Copy the code

The server obtains the parameter information brought by the client through req.query, the server can obtain the corresponding data from the database according to the parameter information, so as to realize the conditional index data

The getWithMap() method does the same thing as getWithQuery() and won’t be described here

2.3, with a fixed request header

The getWithQueryAndHeaders() method is a Get request that carries the request parameters and a fixed request header

//Get requests with parameters and request headers. Parameters are suffixes for links. The resulting link is: /Get/getString? name=leavesC&age=24
    // The key of the Header is userName, and the value is leavesC
    private static void getWithQueryAndHeaders(a) {
        GetService getService = buildRetrofit().create(GetService.class);
        getService.getWithQueryAndHeaders("leavesC".24).enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.isSuccessful()) {
                    try {
                        / / the data returned is: {" code ": 1," MSG ":" success ", "data" : {" name ":" leavesC ", "mobile" : 123456}}
                        System.out.println("onResponse body: " + response.body().string());
                    } catch(IOException e) { e.printStackTrace(); }}else {
                    System.out.println("onResponse code: "+ response.code()); }}@Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                System.out.println("onFailure: "+ t.getMessage()); }}); }Copy the code

At this time, the log information printed by the server is as follows. You can see that the header information contains username, and the value is the same as that stated in the annotation

Parameter Key is: name, value is: leavesC Parameter Key is: age, value is: 24 Header information Key is: username, value is: LeavesC2 Header information Key is: host, value is: localhost:1995 Header information Key is: Connection, value is: keep-alive header information Key is: Accept-encoding, value is: gzip header key is: user-agent, value is: okhttp/3.10.0 Url: /Get/getString? name=leavesC&age=24Copy the code

The header information can be used to authenticate the source of the access, that is, to authenticate the identity of the client

On the server side, I judged the value of the header information whose key value is userName. If the client contains the header information whose key value is userName, but its value is not leavesC, the returned Json data will indicate parameter error

Modifies the value of the header information carried by the getWithQueryAndHeaders() method

/** ** Author: chenZY * Time: 2018/5/29 18:54 * Description: */
public interface GetService {

    // Get requests with request parameters and fixed headers
    @GET("Get/getString")
    @Headers({"userName:leavesC_2"})
    Call<ResponseBody> getWithQueryAndHeaders(@Query("name") String name, @Query("age") int age);

}
Copy the code

The data returned by the server will be

{"code":1001."msg":"Parameter error"}
Copy the code

2.4. Request headers with non-fixed values

The @header annotation used to mark non-fixed value request headers is applied to method parameters, enabling dynamic assignment of request headers

//Get request headers with parameters and non-fixed values. The parameters will be the suffix of the link. The generated link is: /Get/getString? name=leavesC&age=24
    // The key of the Header is userName and the value is Hi
    private static void getWithQueryAndHeader(a) {
        GetService getService = buildRetrofit().create(GetService.class);
        getService.getWithQueryAndHeader("Hi"."leavesC".24).enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.isSuccessful()) {
                    try {
                        / / the data returned is: {" code ": 1," MSG ":" success ", "data" : {" name ":" leavesC ", "mobile" : 123456}}
                        System.out.println("onResponse body: " + response.body().string());
                    } catch(IOException e) { e.printStackTrace(); }}else {
                    System.out.println("onResponse code: "+ response.code()); }}@Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                System.out.println("onFailure: "+ t.getMessage()); }}); }Copy the code

The server prints the following log, which is not much different from the @headers annotation except that one value is fixed and the other is assigned dynamically at run time

Key is: name, value is: leavesC Key is: age, value is:24Header key is: username, value is: Hi Header key is: host, value is: localhost:1995Header information Key is: connection, value is: keep-alive header information Key is: accept-encoding, value is: gzip header information Key is: user-agent , value is: okhttp/3.10. 0Url: / Get/Get string? name=leavesC&age=24
Copy the code

2.5. Specify an access path

There is also a way to add access parameters to the link as an actual part of the link

The corresponding client method is

    @GET("Get/getString/{id}")
    Call<ResponseBody> getWithPath(@Path("id") int id);
Copy the code

In this case, you need to write another Get interface on the server. The :id in /Get/getString/:id indicates that the client can access the callback function of the interface only when the client explicitly carries the parameter value (no Key is required)

app.get('/Get/getString/:id'.function (req, res) {
    // Request parameters
    var query = req.query;
    for (var key in query) {
        console.log("Key is:", key, " , value is: ", query[key]);
    }
    / / request header
    var headers = req.headers;
    for (var key in headers) {
        console.log(Header key is:, key, " , value is: ", headers[key]);
    }
    / / links
    console.log("Url:", req.url);

    var id = req.params.id;
    if (id <= 0) {
        resultJson.onParamsError(res);
    } else {
        var data = {};
        data.name = 'leavesC_' + id;
        data.mobile = 123456; resultJson.onSuccess(res, data); }});Copy the code

The client accesses the interface

	// The generated link is: /Get/getString/22
    private static void getWithPath(a) {
        GetService getService = buildRetrofit().create(GetService.class);
        getService.getWithPath(22).enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.isSuccessful()) {
                    try {
                        / / return data: {" code ": 1," MSG ":" success ", "data" : {" name ":" leavesC_22 ", "mobile" : 123456}}
                        System.out.println("onResponse body: " + response.body().string());
                    } catch(IOException e) { e.printStackTrace(); }}else {
                    System.out.println("onResponse code: "+ response.code()); }}@Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                System.out.println("onFailure: "+ t.getMessage()); }}); }Copy the code

Log information displayed on the server is as follows

Header information Key is: host, value is: localhost:1995Header information Key is: connection, value is: keep-alive header information Key is: accept-encoding, value is: gzip header information Key is: user-agent , value is: okhttp/3.10. 0Url: / Get/Get string22
Copy the code

2.6. Get the Json array

The previous requests are all Json objects. Here we write a return data format of the Josn array interface. Each Json object corresponds to the following Java Bean

/** ** Author: chenZY * Time: 2018/5/26 15:13 * Description: */
public class User {

    private String name;

    private String mobile;

    public User(String name, String mobile) {
        this.name = name;
        this.mobile = mobile; }...}Copy the code

The server interface is used to obtain information about the number user whose ID starts from startId

app.get('/Get/getUser/:startId/:number'.function (req, res) {
    // Request parameters
    var query = req.query;
    for (var key in query) {
        console.log("Key is:", key, " , value is: ", query[key]);
    }
    / / request header
    var headers = req.headers;
    for (var key in headers) {
        console.log(Header key is:, key, " , value is: ", headers[key]);
    }
    / / links
    console.log("Url:", req.url);

    // In order to prevent the parameters brought by the client from being non-numeric types, we need to check their types here
    var startId = parseInt(req.params.startId);
    var number = parseInt(req.params.number);
    console.log("startId: ", startId);
    console.log("number: ", number);
    if (!isNaN(startId) && !isNaN(number) && startId > 0 && number > 0) {
        var items = [];
        for (var index = 0; index < number; index++) {
            var item = {};
            item.name = 'leavesC_' + (startId + index);
            item.mobile = 123456;
            items.push(item);
        }
        resultJson.onSuccess(res, items);
    } else{ resultJson.onParamsError(res); }});Copy the code

The client uses Converter-gson to automatically parse the Json array returned by the server. Since the resultjson. js file unified the data format returned by the server, generics can be used for encapsulation in order to avoid writing code and MSG parameters every time

/** * Author: chenZY * Time: 2018/5/26 15:10 * Description: */
public class Response {

    private int code;

    privateString msg; ...}Copy the code

If the data returned by the server is a Json object, the EntityResponse is passed in via generics to the actual Java Bean

/** ** Author: chenZY * Time: 2018/5/26 15:11 * Description: */
public class EntityResponse<T> extends Response {

    private T data;

    public T getData(a) {
        return data;
    }

    public void setData(T data) {
        this.data = data; }}Copy the code

If the data returned by the server is a Json array, the actual Java Bean is passed in via generics, using ListResponse

/** ** Author: chenZY * Time: 2018/5/26 15:12 * Description: */
public class ListResponse<T> extends Response {

    private List<T> data;

    public List<T> getData(a) {
        return data;
    }

    public void setData(List<T> data) {
        this.data = data; }}Copy the code

The data contained in the List can now be retrieved directly from the callback function

private static void getWithGsonConverter(a) {
        GetService getService = buildRetrofit().create(GetService.class);
        getService.getWithGsonConverter(24.4).enqueue(new Callback<ListResponse<User>>() {
            @Override
            public void onResponse(Call<ListResponse<User>> call, Response<ListResponse<User>> response) {
                if (response.isSuccessful()) {
                    List<User> userList = response.body().getData();
                    if (userList == null) {
                        System.out.println("onResponse: userList == null");
                    } else {
                        for (User user : userList) {
                            System.out.println("onResponse: "+ user); }}}else {
                    System.out.println("onResponse code: "+ response.code()); }}@Override
            public void onFailure(Call<ListResponse<User>> call, Throwable t) {
                System.out.println("onFailure: "+ t.getMessage()); }}); }Copy the code

Logs displayed on the client are as follows

onResponse: User{name='leavesC_24', mobile='123456'}
onResponse: User{name='leavesC_25', mobile='123456'}
onResponse: User{name='leavesC_26', mobile='123456'}
onResponse: User{name='leavesC_27', mobile='123456'}
Copy the code

A Post request

The server Post interface is similar to the Get interface. The main difference lies in the method of obtaining the parameters of the client Post interface

app.post('/Post/postUser'.function (req, res) {
    var body = req.body;
    for (var key in body) {
        console.log("Body key is:", key, " , value is: ", body[key]);
    }
    / / request header
    var headers = req.headers;
    for (var key in headers) {
        console.log("Headers header key is:", key, " , value is: ", headers[key]);
    }
    / / links
    console.log("Url:", req.url);

    var data = {};
    data.name = 'leavesC';
    data.mobile = 123456;
    resultJson.onSuccess(res, data);
});
Copy the code

The PostService interface created by the client is used to declare the methods for accessing the Post interface. Each method contains different parameter values. The differences between the methods are identified according to the logs printed by the server

The @formurlencoded annotation indicates that the request header is a Form, corresponding to the content-Type request header that the client accesses the interface

/ * * * the author: chenZY * time: 2018/5/29 18:54 * description: https://github.com/leavesC * /
public interface PostService {

    @FormUrlEncoded
    @POST("Post/postUser")
    Call<ResponseBody> postWithField(@Field("name") String name, @Field("mobile") String mobile);

    @FormUrlEncoded
    @POST("Post/postUser")
    Call<ResponseBody> postWithFieldMap(@FieldMap Map<String, String> map);

    @POST("Post/postUser")
    Call<ResponseBody> postWithBody(@Body User user);

}
Copy the code
private static void postWithField(a) {
        PostService postService = buildRetrofit().create(PostService.class);
        postService.postWithField("czy"."123456").enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.isSuccessful()) {
                    try {
                        / / return data: {" code ": 1," MSG ":" success ", "data" : {" name ":" leavesC ", "mobile" : 123456}}
                        System.out.println("onResponse body: " + response.body().string());
                    } catch(IOException e) { e.printStackTrace(); }}else {
                    System.out.println("onResponse code: "+ response.code()); }}@Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                System.out.println("onFailure: "+ t.getMessage()); }}); }Copy the code

The server prints the log as shown below, and you can see that the client carries past parameter values. In addition, the header “Content-Type” is the @formurlencoded annotation for the client interface method

Body parameter Key is: name, value is: CZY Body parameter key is: mobile, value is: czy123456Headers header key is: Content-type, value is: Application/X-www-form-urlencoded Headers header key is: Content-type, value is: Application/X-www-form-urlencoded Headers header key is: Content-type, value is: Application/X-www-form-urlencoded Headers header key is: content-length , value is:22Headers header information Key is: host, value is: localhost:1995Headers header information Key is: connection, value is: keep-alive Headers header information Key is: accept-encoding, value is: Gzip Headers header Information Key is: user-agent, value is: okhttp/3.10. 0Url: / Post/postUserCopy the code

Pass parameters via @FieldMap and @body annotations in the same way as @Field, Retrofit iterates through all the fields contained in the parameters to generate the parameters to pass, which I won’t go into here

Upload a file

Upload files with parameters

Here to simulate the client to upload a picture to the server, with parameter values

app.post('/uploadPhoto'.function (req, res) {
    var body = req.body;
    for (var key in body) {
        console.log("Body key is:", key, " , value is: ", body[key]);
    }
    / / request header
    var headers = req.headers;
    for (var key in headers) {
        console.log("Headers header key is:", key, " , value is: ", headers[key]);
    }
    / / links
    console.log("Url:", req.url);

    // Generate the multiparty object and configure the upload destination path
    var form = new multiparty.Form({uploadDir: '.. /public/upload/'});
    // Fields contains the parameter values passed in
    //files represents the file object uploaded to the server
    // The file sent from the client will be automatically saved to the specified folder in the background, and the processing result will be notified through the callback function
    form.parse(req, function (err, fields, files) {
        if (err) {
            resultJson.onSystemError(res);
        } else {
            console.log("fields : ", fields);
            console.log("files : ", files);
            var filesContent = files['photo'] [0];
            vardata = {}; data.filePath = filesContent.path; resultJson.onSuccess(res, data); }}); });Copy the code

The @multipart annotation indicates that the request body is a Form that supports file uploading. It corresponds to the request header whose key value is “Content-Type” when the client accesses the interface

In addition, three @Part annotations are used in the method parameters, the first to annotate the file object to be uploaded, and the remaining two to indicate the request parameters to be carried along with the uploaded file

/** * Author: chenZY * Time: 2018/5/29 18:55 * Description: */
public interface UploadService {

    @Multipart
    @POST("uploadPhoto")
    Call<ResponseBody> uploadPhoto(@Part MultipartBody.Part photo, @Part("userName") RequestBody username, @Part("password") RequestBody password);

}
Copy the code

The images are placed under the resources folder of the project

private static void uploadPhoto(a) {
        UploadService uploadService = buildRetrofit().create(UploadService.class);
        File file = new File(".. \\JavaRetrofit\\src\\main\\resources\\images\\lufei.jpg");
        RequestBody photoRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
        / / set the Content - Disposition: the form - data; name="photo"; filename="lufei.jpg"
        MultipartBody.Part photo = MultipartBody.Part.createFormData("photo", file.getName(), photoRequestBody);
        RequestBody userName = RequestBody.create(MediaType.parse("text/plain"), "leavesC");
        RequestBody password = RequestBody.create(MediaType.parse("text/plain"), "123456");
        uploadService.uploadPhoto(photo, userName, password).enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.isSuccessful()) {
                    try {
                        System.out.println("onResponse body: " + response.body().string());
                    } catch(IOException e) { e.printStackTrace(); }}else {
                    System.out.println("onResponse code: "+ response.code()); }}@Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                System.out.println("onFailure: "+ t.getMessage()); }}); }Copy the code

After the code for uploading files is run, the log information displayed on the server is as follows

Headers header information Key is: content-type, value is: multipart/form-data; boundary=3b8bf455- 620.a- 4250.- 8 -f3d- 8079.Df43d090 Headers Header information key is: Content-Length, value is:224722Headers header information Key is: host, value is: localhost:1995Headers header information Key is: connection, value is: keep-alive Headers header information Key is: accept-encoding, value is: Gzip Headers header Information Key is: user-agent, value is: okhttp/3.10. 0/uploadPhoto fields: {userName: ['leavesC'], password: ['123456' ] }
files :  { photo:
   [ { fieldName: 'photo',
       originalFilename: 'lufei.jpg',
       path: '..\\public\\upload\\eKPBTufrJs24ybaoOA2HQ3Aj.jpg',
       headers: [Object],
       size: 224115}}]Copy the code

The data returned by the server is shown below

{"code":1."msg":"success"."data": {"filePath":".. \\public\\upload\\lfCMVA2VXLNN8XaRmpl-9nE7.jpg"}}
Copy the code

At this point, you can see that the upload folder of the server project has an extra randomly named image

Multi-file upload

Here to achieve multiple files uploaded at the same time

Because the client uses different parameter Settings to implement multiple file uploading, the server needs to adopt different data parsing methods because a new interface is enabled

app.post('/uploadFileDouble'.function (req, res) {
    var body = req.body;
    for (var key in body) {
        console.log("Body key is:", key, " , value is: ", body[key]);
    }
    / / request header
    var headers = req.headers;
    for (var key in headers) {
        console.log("Headers header key is:", key, " , value is: ", headers[key]);
    }
    / / links
    console.log("Url:", req.url);

    // Generate the multiparty object and configure the upload destination path
    var form = new multiparty.Form({uploadDir: '.. /public/upload/'});
    // Fields contains the parameter values passed in
    //files represents the file object uploaded to the server
    // The file sent from the client will be automatically saved to the specified folder in the background, and the processing result will be notified through the callback function
    form.parse(req, function (err, fields, files) {
        if (err) {
            resultJson.onSystemError(res);
        } else {
            console.log("fields : ", fields);
            console.log("files : ", files);
            var filesContent = files['photos'];
            var items = [];
            for (var index in filesContent) {
                varitem = {}; item.filePath = filesContent[index].path; items.push(item); } resultJson.onSuccess(res, items); }}); });Copy the code

The client interface method for uploading multiple files is marked with the @partMap annotation, which holds multiple file forms that need to be uploaded

/** * Author: chenZY * Time: 2018/5/29 18:55 * Description: */
public interface UploadService {

    @Multipart
    @POST("uploadFileDouble")
    Call<ResponseBody> uploadFileDouble(@PartMap Map<String, RequestBody> files);

}
Copy the code
private static void uploadFileDouble(a) {
        UploadService uploadService = buildRetrofit().create(UploadService.class);
        Map<String, RequestBody> photoMap = new HashMap<>();

        File file = new File(".. \\JavaRetrofit\\src\\main\\resources\\images\\lufei.jpg");
        RequestBody photoRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
        photoMap.put("photos\"; filename=\"" + file.getName(), photoRequestBody);

        file = new File(".. \\JavaRetrofit\\src\\main\\resources\\images\\mingren.jpg");
        photoRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
        photoMap.put("photos\"; filename=\"" + file.getName(), photoRequestBody);

        uploadService.uploadFileDouble(photoMap).enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.isSuccessful()) {
                    try {
                        System.out.println("onResponse body: " + response.body().string());
                    } catch(IOException e) { e.printStackTrace(); }}else {
                    System.out.println("onResponse code: "+ response.code()); }}@Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                System.out.println("onFailure: "+ t.getMessage()); }}); }Copy the code

Run the program, you can see that there are two more different pictures under the upload folder. The log output by the server is as follows

Headers header information Key is: content-type, value is: multipart/form-data; boundary=5c3fcbbb-dd78- 4854.-ad12- 3C4ae3fd1f02 Headers header information Key is: Content-Length, value is:347838Headers header information Key is: host, value is: localhost:1995Headers header information Key is: connection, value is: keep-alive Headers header information Key is: accept-encoding, value is: Gzip Headers header Information Key is: user-agent, value is: okhttp/3.10. 0Url: /uploadFileDouble Fields: {} files: {photos: [{fieldName: 'photos', originalFilename: 'mingren.jpg', path: '..\\public\\upload\\HsvSfjgKtLL3gAqwrxRFk5G-.jpg', headers: [Object], size:123255}, { fieldName: 'photos', originalFilename: 'lufei.jpg', path: '.. \\public\\upload\\bicNIvOD3ZcBe8EgqmSd9SFf.jpg', headers: [Object], size:224115}}]Copy the code

The data received by the client is as follows

{"code":1."msg":"success"."data": [{"filePath":".. \\public\\upload\\HsvSfjgKtLL3gAqwrxRFk5G-.jpg"}, {"filePath":".. \\public\\upload\\bicNIvOD3ZcBe8EgqmSd9SFf.jpg"}}]Copy the code

The download file

Express is highly encapsulated for file downloads, so it may be easier than you think for a server to provide file downloads

This points the file to be downloaded directly to an image in the uplaod folder

app.get('/downloadFile'.function (req, res) {
    // File storage path
    var filePath = '.. /public/upload/Anoj-VQ-cd_vkw9_O5ErSSG6.jpg';
    // Set the file name displayed when the file is downloaded. If this parameter is not set, use the original file name
    var fileName = 'leavesC.jpg';
    res.download(filePath, fileName);
});
Copy the code

The client creates a DownloadService to declare a method that provides the download function. In order to support large file downloads, the @Streaming annotation is used here to avoid loading the entire file into memory and causing an OOM on Android

/** ** Author: chenZY * Time: 2018/5/30 13:54 * Description: */
public interface DownloadService {

    @Streaming
    @GET
    Call<ResponseBody> downloadFile(@Url String fileUrl);

}
Copy the code

As you can see, the downloaded file is written directly to the desktop using the file read and write method provided by the okIo package

private static void downloadFile(a) {
        DownloadService downloadService = buildRetrofit().create(DownloadService.class);
        Call<ResponseBody> call = downloadService.downloadFile("downloadFile");
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.isSuccessful()) {
                    BufferedSink sink = null;
                    try {
                        File file = new File("C:\\Users\\CZY\\Desktop\\Hi.jpg");
                        sink = Okio.buffer(Okio.sink(file));
                        sink.writeAll(response.body().source());
                        System.out.println("onResponse : success");
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            if(sink ! =null) { sink.close(); }}catch(IOException e) { e.printStackTrace(); }}}else {
                    System.out.println("onResponse code: "+ response.code()); }}@Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                System.out.println("onFailure: "+ t.getMessage()); }}); }Copy the code

In addition, if the above code is run on Android system, there is a problem. Because the Callback function is called in the main thread, if the Callback function is performed in the main thread for a long time, it may cause ANR

That’s the end of the example between Retrofit2 and the server. In addition to providing the source code for the client, I’ve also packaged the entire server project together for download

Project home: Retrofit2Samples -> github.com/leavesC/Ret…