Home | History | Annotate | Download | only in toolbox
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.volley.toolbox;
     18 
     19 import com.android.volley.Cache;
     20 import com.android.volley.NetworkResponse;
     21 
     22 import org.apache.http.Header;
     23 import org.apache.http.message.BasicHeader;
     24 import org.junit.Before;
     25 import org.junit.Test;
     26 import org.junit.runner.RunWith;
     27 import org.robolectric.RobolectricTestRunner;
     28 
     29 import java.text.DateFormat;
     30 import java.text.SimpleDateFormat;
     31 import java.util.Date;
     32 import java.util.HashMap;
     33 import java.util.Locale;
     34 import java.util.Map;
     35 
     36 import static org.junit.Assert.*;
     37 
     38 @RunWith(RobolectricTestRunner.class)
     39 public class HttpHeaderParserTest {
     40 
     41     private static long ONE_MINUTE_MILLIS = 1000L * 60;
     42     private static long ONE_HOUR_MILLIS = 1000L * 60 * 60;
     43     private static long ONE_DAY_MILLIS = ONE_HOUR_MILLIS * 24;
     44     private static long ONE_WEEK_MILLIS = ONE_DAY_MILLIS * 7;
     45 
     46     private NetworkResponse response;
     47     private Map<String, String> headers;
     48 
     49     @Before public void setUp() throws Exception {
     50         headers = new HashMap<String, String>();
     51         response = new NetworkResponse(0, null, headers, false);
     52     }
     53 
     54     @Test public void parseCacheHeaders_noHeaders() {
     55         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
     56 
     57         assertNotNull(entry);
     58         assertNull(entry.etag);
     59         assertEquals(0, entry.serverDate);
     60         assertEquals(0, entry.lastModified);
     61         assertEquals(0, entry.ttl);
     62         assertEquals(0, entry.softTtl);
     63     }
     64 
     65     @Test public void parseCacheHeaders_headersSet() {
     66         headers.put("MyCustomHeader", "42");
     67 
     68         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
     69 
     70         assertNotNull(entry);
     71         assertNotNull(entry.responseHeaders);
     72         assertEquals(1, entry.responseHeaders.size());
     73         assertEquals("42", entry.responseHeaders.get("MyCustomHeader"));
     74     }
     75 
     76     @Test public void parseCacheHeaders_etag() {
     77         headers.put("ETag", "Yow!");
     78 
     79         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
     80 
     81         assertNotNull(entry);
     82         assertEquals("Yow!", entry.etag);
     83     }
     84 
     85     @Test public void parseCacheHeaders_normalExpire() {
     86         long now = System.currentTimeMillis();
     87         headers.put("Date", rfc1123Date(now));
     88         headers.put("Last-Modified", rfc1123Date(now - ONE_DAY_MILLIS));
     89         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
     90 
     91         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
     92 
     93         assertNotNull(entry);
     94         assertNull(entry.etag);
     95         assertEqualsWithin(entry.serverDate, now, ONE_MINUTE_MILLIS);
     96         assertEqualsWithin(entry.lastModified, (now - ONE_DAY_MILLIS), ONE_MINUTE_MILLIS);
     97         assertTrue(entry.softTtl >= (now + ONE_HOUR_MILLIS));
     98         assertTrue(entry.ttl == entry.softTtl);
     99     }
    100 
    101     @Test public void parseCacheHeaders_expiresInPast() {
    102         long now = System.currentTimeMillis();
    103         headers.put("Date", rfc1123Date(now));
    104         headers.put("Expires", rfc1123Date(now - ONE_HOUR_MILLIS));
    105 
    106         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
    107 
    108         assertNotNull(entry);
    109         assertNull(entry.etag);
    110         assertEqualsWithin(entry.serverDate, now, ONE_MINUTE_MILLIS);
    111         assertEquals(0, entry.ttl);
    112         assertEquals(0, entry.softTtl);
    113     }
    114 
    115     @Test public void parseCacheHeaders_serverRelative() {
    116 
    117         long now = System.currentTimeMillis();
    118         // Set "current" date as one hour in the future
    119         headers.put("Date", rfc1123Date(now + ONE_HOUR_MILLIS));
    120         // TTL four hours in the future, so should be three hours from now
    121         headers.put("Expires", rfc1123Date(now + 4 * ONE_HOUR_MILLIS));
    122 
    123         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
    124 
    125         assertEqualsWithin(now + 3 * ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
    126         assertEquals(entry.softTtl, entry.ttl);
    127     }
    128 
    129     @Test public void parseCacheHeaders_cacheControlOverridesExpires() {
    130         long now = System.currentTimeMillis();
    131         headers.put("Date", rfc1123Date(now));
    132         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
    133         headers.put("Cache-Control", "public, max-age=86400");
    134 
    135         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
    136 
    137         assertNotNull(entry);
    138         assertNull(entry.etag);
    139         assertEqualsWithin(now + ONE_DAY_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
    140         assertEquals(entry.softTtl, entry.ttl);
    141     }
    142 
    143     @Test public void testParseCacheHeaders_staleWhileRevalidate() {
    144         long now = System.currentTimeMillis();
    145         headers.put("Date", rfc1123Date(now));
    146         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
    147 
    148         // - max-age (entry.softTtl) indicates that the asset is fresh for 1 day
    149         // - stale-while-revalidate (entry.ttl) indicates that the asset may
    150         // continue to be served stale for up to additional 7 days
    151         headers.put("Cache-Control", "max-age=86400, stale-while-revalidate=604800");
    152 
    153         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
    154 
    155         assertNotNull(entry);
    156         assertNull(entry.etag);
    157         assertEqualsWithin(now + ONE_DAY_MILLIS, entry.softTtl, ONE_MINUTE_MILLIS);
    158         assertEqualsWithin(now + ONE_DAY_MILLIS + ONE_WEEK_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
    159     }
    160 
    161     @Test public void parseCacheHeaders_cacheControlNoCache() {
    162         long now = System.currentTimeMillis();
    163         headers.put("Date", rfc1123Date(now));
    164         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
    165         headers.put("Cache-Control", "no-cache");
    166 
    167         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
    168 
    169         assertNull(entry);
    170     }
    171 
    172     @Test public void parseCacheHeaders_cacheControlMustRevalidateNoMaxAge() {
    173         long now = System.currentTimeMillis();
    174         headers.put("Date", rfc1123Date(now));
    175         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
    176         headers.put("Cache-Control", "must-revalidate");
    177 
    178         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
    179         assertNotNull(entry);
    180         assertNull(entry.etag);
    181         assertEqualsWithin(now, entry.ttl, ONE_MINUTE_MILLIS);
    182         assertEquals(entry.softTtl, entry.ttl);
    183     }
    184 
    185     @Test public void parseCacheHeaders_cacheControlMustRevalidateWithMaxAge() {
    186         long now = System.currentTimeMillis();
    187         headers.put("Date", rfc1123Date(now));
    188         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
    189         headers.put("Cache-Control", "must-revalidate, max-age=3600");
    190 
    191         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
    192         assertNotNull(entry);
    193         assertNull(entry.etag);
    194         assertEqualsWithin(now + ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
    195         assertEquals(entry.softTtl, entry.ttl);
    196     }
    197 
    198     @Test public void parseCacheHeaders_cacheControlMustRevalidateWithMaxAgeAndStale() {
    199         long now = System.currentTimeMillis();
    200         headers.put("Date", rfc1123Date(now));
    201         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
    202 
    203         // - max-age (entry.softTtl) indicates that the asset is fresh for 1 day
    204         // - stale-while-revalidate (entry.ttl) indicates that the asset may
    205         // continue to be served stale for up to additional 7 days, but this is
    206         // ignored in this case because of the must-revalidate header.
    207         headers.put("Cache-Control",
    208                 "must-revalidate, max-age=86400, stale-while-revalidate=604800");
    209 
    210         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
    211         assertNotNull(entry);
    212         assertNull(entry.etag);
    213         assertEqualsWithin(now + ONE_DAY_MILLIS, entry.softTtl, ONE_MINUTE_MILLIS);
    214         assertEquals(entry.softTtl, entry.ttl);
    215     }
    216 
    217     private void assertEqualsWithin(long expected, long value, long fudgeFactor) {
    218         long diff = Math.abs(expected - value);
    219         assertTrue(diff < fudgeFactor);
    220     }
    221 
    222     private static String rfc1123Date(long millis) {
    223         DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH);
    224         return df.format(new Date(millis));
    225     }
    226 
    227     // --------------------------
    228 
    229     @Test public void parseCharset() {
    230         // Like the ones we usually see
    231         headers.put("Content-Type", "text/plain; charset=utf-8");
    232         assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
    233 
    234         // Charset specified, ignore default charset
    235         headers.put("Content-Type", "text/plain; charset=utf-8");
    236         assertEquals("utf-8", HttpHeaderParser.parseCharset(headers, "ISO-8859-1"));
    237 
    238         // Extra whitespace
    239         headers.put("Content-Type", "text/plain;    charset=utf-8 ");
    240         assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
    241 
    242         // Extra parameters
    243         headers.put("Content-Type", "text/plain; charset=utf-8; frozzle=bar");
    244         assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
    245 
    246         // No Content-Type header
    247         headers.clear();
    248         assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
    249 
    250         // No Content-Type header, use default charset
    251         headers.clear();
    252         assertEquals("utf-8", HttpHeaderParser.parseCharset(headers, "utf-8"));
    253 
    254         // Empty value
    255         headers.put("Content-Type", "text/plain; charset=");
    256         assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
    257 
    258         // None specified
    259         headers.put("Content-Type", "text/plain");
    260         assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
    261 
    262         // None charset specified, use default charset
    263         headers.put("Content-Type", "application/json");
    264         assertEquals("utf-8", HttpHeaderParser.parseCharset(headers, "utf-8"));
    265 
    266         // None specified, extra semicolon
    267         headers.put("Content-Type", "text/plain;");
    268         assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
    269     }
    270 
    271     @Test public void parseCaseInsensitive() {
    272 
    273         long now = System.currentTimeMillis();
    274 
    275         Header[] headersArray = new Header[5];
    276         headersArray[0] = new BasicHeader("eTAG", "Yow!");
    277         headersArray[1] = new BasicHeader("DATE", rfc1123Date(now));
    278         headersArray[2] = new BasicHeader("expires", rfc1123Date(now + ONE_HOUR_MILLIS));
    279         headersArray[3] = new BasicHeader("cache-control", "public, max-age=86400");
    280         headersArray[4] = new BasicHeader("content-type", "text/plain");
    281 
    282         Map<String, String> headers = BasicNetwork.convertHeaders(headersArray);
    283         NetworkResponse response = new NetworkResponse(0, null, headers, false);
    284         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
    285 
    286         assertNotNull(entry);
    287         assertEquals("Yow!", entry.etag);
    288         assertEqualsWithin(now + ONE_DAY_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
    289         assertEquals(entry.softTtl, entry.ttl);
    290         assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
    291     }
    292 }
    293