简体   繁体   中英

Retrofit ignores query parameter

I am trying to parse a JSON file with Retrofit and Gson. The problem is that the URL accepts a parameter forceDeviceId . If this value is set to -111 the correct file can be retreived. By default this parameter is set to -1 and a 404 error is thrown:

Not working https://www.mediamarkt.de/de/product/productlistajax.json?categoryId=563612&sort=topseller&lazyLoading=true&forceDeviceId=-1

Working https://www.mediamarkt.de/de/product/productlistajax.json?categoryId=563612&sort=topseller&lazyLoading=true&forceDeviceId=-111

I tried two approaches: hardcoding the parameter in the API url and using a Query:

// either hardcoded in the URL
@GET("de/product/productlistajax.json?forceDeviceId=-111")
  Call<ProductListParent> loadProductList(
        @Query("categoryId") String categoryId,
        @Query("sort") String sort,
        @Query("lazyLoading") String lazyLoading,
        // alternatively use this query
        @Query("forceDeviceId") String forceDeviceId
);

But both approaches return a 404. So I wonder what I am missing to make it work (as it is in the browser). I also recognized that the parameter is deleted immediatley after the URL in the browser is loaded. So is this something their backend prevents?

Here is the method in which i am calling:

  @Subscribe
    public void getProductListRequest(MMSATServices.ProductListRequest request) {
        final MMSATServices.ProductListResponse productListResponse = new MMSATServices.ProductListResponse();
        productListResponse.productListParent = new ProductListParent();

        Call<ProductListParent> call = liveApi.loadProductList(
                request.categoryId, request.sort, request.lazyLoading, request.forceDeviceId);
        call.enqueue(new Callback<ProductListParent>() {
            @Override
            public void onResponse(Call<ProductListParent> call, Response<ProductListParent> response) {
                productListResponse.productListParent = response.body();
                bus.post(productListResponse);
            }

            @Override
            public void onFailure(Call<ProductListParent> call, Throwable t) {
                t.printStackTrace();
            }
        });
    }

This is the error message I get:

Response{protocol=http/1.1, code=404, message=Not Found, url=https://www.mediamarkt.de/de/product/productlistajax.json?categoryId=563612&sort=topseller&lazyLoading=true}

EDIT: here is how I create the Retrofit object

private static Retrofit createMMSATService(String baseUrl) {
    HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
    interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

    OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(interceptor)
            .cookieJar(new CustomCookieJar())
            .build();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    return retrofit;
}

with the CustomCookieJAr class:

public class CustomCookieJar implements CookieJar {

    private List<Cookie> cookies = new ArrayList<>();

    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        if (cookies != null) {
            this.cookies = cookies;
        }
    }

    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
        return cookies;
    }
}

2 things append when you execute this request, firstly you get a 302 response and then OkHTTP execute the request at the new location provided by the 302 request. The second request fails with a 404 response. I've found that the 302 response has 2 new cookies in his headers : MC_DEVICE_ID and MC_DEVICE_ID_EXT . So I've configured OkHTTP (the request engine used by Retrofit) to send the cookies received in the 302 response to the second request and it works !

That's explain why it works in a web browser, because a web browser store the returned cookies and send it again for the second request. OkHTTP doesn't have this mechanism by default, you have to manually set the cookies from the 302 response in the new request.

There is my code :

 Retrofit retrofit = new Retrofit.Builder()
            .client(new OkHttpClient.Builder()
                    .cookieJar(new CookieJar() {
                        private List<Cookie> cookies = new ArrayList<>();
                        @Override
                        public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                            if (cookies != null) {
                                this.cookies = cookies;
                            }
                        }

                        @Override
                        public List<Cookie> loadForRequest(HttpUrl url) {
                            return cookies;
                        }
                    }).build())
            .baseUrl("https://www.mediamarkt.de/")
            .build()

Previous answer

The Retrofit/OkHTTP stacktrace is really hard to decode even if I'm debugging but, as far as can I see, the url https://www.mediamarkt.de/de/product/productlistajax.json?categoryId=563612&sort=topseller&lazyLoading=true&forceDeviceId=-111 returns a 302 http code (Moved temporarily) and returns the new address in the response header.

The problem is that the url provided by the header location doesn't contains the query param forceDeviceId . Its a bug from the mediamarkt API, I suggest you to report this bug to mediamarkt developers and find an other way to send this param for the moment (I can't help more, I don't understand the german langage).

Some details :

The response at the first call that returns a 302 : Response{protocol=http/1.1, code=302, message=Found, url=https://www.mediamarkt.de/de/product/productlistajax.json?forceDeviceId=-111&categoryId=563612&sort=topseller&lazyLoading=true}

OkHTTP get the new location by calling this :

String location = userResponse.header("Location");

(See here RetryAndFollowUpInterceptor.java:301 )

The value of location is /de/product/productlistajax.json?categoryId=563612&sort=topseller&lazyLoading=true . As you can see the param forceDeviceId is missing :/

You don't need to include query parameter in your GET Annotation. Below codes work fine for me

@GET("de/product/productlistajax.json")
Call<AppConfigParent> loadAppConfigParent(@Query("forceDeviceId") String forceDeviceId);

Send this parameter with other parameters

   // either hardcoded in the URL
@GET("de/product/productlistajax.json")
  Call<ProductListParent> loadProductList(
        @Query("forceDeviceId") String forceDeviceId,
        @Query("categoryId") String categoryId,
        @Query("sort") String sort,
        @Query("lazyLoading") String lazyLoading,
        // alternatively use this query
        @Query("forceDeviceId") String forceDeviceId
);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM