19

Trying to use Volley lib as a network wrapper for my android application. I have a connection up and running, but the problem is that every time there is multiple "Set-Cookie" headers in the response Volley uses Map that cannot have duplicate keys, and will only store the last Set-cookie header and overwrite the rest.

Is there a workaround for this issue?

Is there another lib to use?

Cœur
  • 37,241
  • 25
  • 195
  • 267
RompaP
  • 193
  • 1
  • 5
  • 2
    Just notice this myself, this is ridiculous from Google. It's obvious this library is intended for very lightweight stuff. – georgiecasey Oct 26 '13 at 01:31
  • Its not a problem with Android Volley. Its a problem of the web servers. Set-Cookie cannot be multiple. http://stackoverflow.com/questions/11533867/set-cookie-header-with-multiple-cookies – nizam.sp Mar 15 '15 at 07:29
  • http://stackoverflow.com/a/25388897/2819864 is the fastest solution – RominaV Nov 23 '16 at 01:06

4 Answers4

17

I tried overiding classes to fix this but when I had to edit NetworkResponse, I was descending too far down the rabbithole. So I decided to just edit Volley directly to grab all response headers in an array and not a Map.

My fork is on GitHub and I included an example usage activity.

I made changes to NetworkResponse.java, BasicNetwork.java and HurlStack.java as detailed in this commit.

Then to use in your actual apps you do something like this

protected Response<String> parseNetworkResponse(NetworkResponse response) {
            // we must override this to get headers. and with the fix, we should get all headers including duplicate names
            // in an array of apache headers called apacheHeaders. everything else about volley is the same
            for (int i = 0; i < response.apacheHeaders.length; i++) {
                String key = response.apacheHeaders[i].getName();
                String value = response.apacheHeaders[i].getValue();
                Log.d("VOLLEY_HEADERFIX",key + " - " +value);
            }

            return super.parseNetworkResponse(response);
        }

It's a dirty little hack but seems to work well for me at the moment.

georgiecasey
  • 21,793
  • 11
  • 65
  • 74
3

The first thing you need is to modify BasicNetwork.convertHeaders method to make it support multiple map values. Here is example of modified method:

protected static Map<String, List<String>> convertHeaders(Header[] headers) {
    Map<String, List<String>> result = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER);
    for (int i = 0; i < headers.length; i++) {
        Header header = headers[i];
        List<String> list = result.get(header.getName());
        if (list == null) {
            list = new ArrayList<String>(1);
            list.add(header.getValue());
            result.put(header.getName(), list);
        }
        else list.add(header.getValue());

    }
    return result;
}

Next thing you need is to modify DiskBasedCache.writeStringStringMap and DiskBasedCache.readStringStringMap methods. They should support multiple values. Here are modified methods along with helper methods:

static void writeStringStringMap(Map<String, List<String>> map, OutputStream os) throws IOException {
    if (map != null) {
        writeInt(os, map.size());
        for (Map.Entry<String, List<String>> entry : map.entrySet()) {
            writeString(os, entry.getKey());
            writeString(os, joinStringsList(entry.getValue()));
        }
    } else {
        writeInt(os, 0);
    }
}

static Map<String, List<String>> readStringStringMap(InputStream is) throws IOException {
    int size = readInt(is);
    Map<String, List<String>> result = (size == 0)
            ? Collections.<String, List<String>>emptyMap()
            : new HashMap<String, List<String>>(size);
    for (int i = 0; i < size; i++) {
        String key = readString(is).intern();
        String value = readString(is).intern();
        result.put(key, parseNullStringsList(value));
    }
    return result;
}

static List<String> parseNullStringsList(String str) {
    String[] strs = str.split("\0");
    return Arrays.asList(strs);
}

static String joinStringsList(List<String> list) {
    StringBuilder ret = new StringBuilder();
    boolean first = true;
    for (String str : list) {
        if (first) first = false;
        else ret.append("\0");
        ret.append(str);
    }
    return ret.toString();
}

And last thing is HttpHeaderParser class. You should make its parseCacheHeaders method support multiple values. Use the following helper method for this:

public static String getHeaderValue(List<String> list) {
    if ((list == null) || list.isEmpty()) return null;
    return list.get(0);
}

And the latest thing to modify is a bunch of places to replace

Map<String, String>

to

Map<String, List<String>>

Use your IDE to do this.

Ruslan Yanchyshyn
  • 2,774
  • 1
  • 24
  • 22
  • Hi @ruslan-yanchyshyn, could you put the full code to see the example? I'm trying to follow your steps, but when I try to override BasicNetwork I have a lot of dependencies. Could you help me? – Adae Rodríguez Nov 22 '16 at 10:40
3

Question pretty old, but if helps someone. In newest volley you have:

protected Response<String> parseNetworkResponse(NetworkResponse response)
{
    List<Header> headers = response.allHeaders;

    String sessionId = null;

    for (Header header : headers)
    {
        // header.getName();
        // header.getValue();
    }

    return super.parseNetworkResponse(response);
}
Ido
  • 2,034
  • 1
  • 17
  • 16
1

You can override Network class of volley. Looking at performRequest and convertHeaders methods of BasicNetwork might help. Then, passing your Network implementation to the contructor of RequestQueue like:

new RequestQueue(new NoCache(), new YourOwnNetwork());

Kazuki
  • 1,462
  • 14
  • 34
  • Hi @Kazuki, could you put and example? I have created my own interface of Network (CustomNetwork) with the perfomRequest. Then I modify the implementation of it but I can't to pass CustomNetwork in the new RequestQueue(...). Could you help me? – Adae Rodríguez Nov 22 '16 at 12:35