URLConnectionTest.java revision b1b5baac449d2725002338735f4db34bec8fd001
1e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes/* 2e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * Copyright (C) 2009 The Android Open Source Project 3e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * 4e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * Licensed under the Apache License, Version 2.0 (the "License"); 5e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * you may not use this file except in compliance with the License. 6e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * You may obtain a copy of the License at 7e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * 8e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * http://www.apache.org/licenses/LICENSE-2.0 9e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * 10e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * Unless required by applicable law or agreed to in writing, software 11e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * distributed under the License is distributed on an "AS IS" BASIS, 12e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * See the License for the specific language governing permissions and 14e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes * limitations under the License. 15e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes */ 16e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes 17e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughespackage java.net; 18e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes 19b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport tests.http.MockResponse; 20b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport tests.http.MockWebServer; 21b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport tests.http.RecordedRequest; 22b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 23e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughesimport java.io.BufferedReader; 246247987eb505a482a67f5f19678260d9e7240a5fElliott Hughesimport java.io.IOException; 25e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughesimport java.io.InputStreamReader; 2602f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughesimport java.io.OutputStream; 2702f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughesimport java.util.Arrays; 286247987eb505a482a67f5f19678260d9e7240a5fElliott Hughesimport java.util.List; 296247987eb505a482a67f5f19678260d9e7240a5fElliott Hughesimport java.util.Map; 30e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes 31e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughespublic class URLConnectionTest extends junit.framework.TestCase { 32b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 33b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson private String readFirstLine(MockWebServer server) throws Exception { 34b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson URLConnection connection = server.getUrl("/").openConnection(); 35e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); 36e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes String result = in.readLine(); 37e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes in.close(); 38e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes return result; 39e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes } 40e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes 41e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes // Check that if we don't read to the end of a response, the next request on the 42e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes // recycled connection doesn't get the unread tail of the first request's response. 43e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes // http://code.google.com/p/android/issues/detail?id=2939 44e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes public void test_2939() throws Exception { 45b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8); 46b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 47b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson MockWebServer server = new MockWebServer(); 48b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson server.enqueue(response); 49b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson server.enqueue(response); 50b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson server.play(); 51b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 52b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson assertTrue(readFirstLine(server).equals("ABCDE")); 53b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson assertEquals(0, server.takeRequest().getSequenceNumber()); 54b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson assertTrue(readFirstLine(server).equals("ABCDE")); 55b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson assertEquals(1, server.takeRequest().getSequenceNumber()); 568baf143a7c8921d07b54adbc66ac1e5b42de5fe6Jesse Wilson } 578baf143a7c8921d07b54adbc66ac1e5b42de5fe6Jesse Wilson 588baf143a7c8921d07b54adbc66ac1e5b42de5fe6Jesse Wilson public void testConnectionsArePooled() throws Exception { 59b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"); 60b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 61b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson MockWebServer server = new MockWebServer(); 62b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson server.enqueue(response); 63b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson server.enqueue(response); 64b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson server.enqueue(response); 65b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson server.play(); 66b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 67b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson readFirstLine(server); 68b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson assertEquals(0, server.takeRequest().getSequenceNumber()); 69b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson readFirstLine(server); 70b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson assertEquals(1, server.takeRequest().getSequenceNumber()); 71b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson readFirstLine(server); 72b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson assertEquals(2, server.takeRequest().getSequenceNumber()); 73e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes } 7402f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes 75b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson enum UploadKind { CHUNKED, FIXED_LENGTH } 76b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS } 7702f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes 7802f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes public void test_chunkedUpload_byteByByte() throws Exception { 7902f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes doUpload(UploadKind.CHUNKED, WriteKind.BYTE_BY_BYTE); 8002f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } 8102f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes 8202f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes public void test_chunkedUpload_smallBuffers() throws Exception { 8302f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes doUpload(UploadKind.CHUNKED, WriteKind.SMALL_BUFFERS); 8402f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } 8502f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes 8602f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes public void test_chunkedUpload_largeBuffers() throws Exception { 8702f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes doUpload(UploadKind.CHUNKED, WriteKind.LARGE_BUFFERS); 8802f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } 8902f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes 9002f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes public void test_fixedLengthUpload_byteByByte() throws Exception { 9102f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes doUpload(UploadKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE); 9202f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } 9302f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes 9402f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes public void test_fixedLengthUpload_smallBuffers() throws Exception { 9502f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes doUpload(UploadKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS); 9602f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } 9702f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes 9802f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes public void test_fixedLengthUpload_largeBuffers() throws Exception { 9902f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes doUpload(UploadKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS); 10002f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } 10102f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes 10202f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes private void doUpload(UploadKind uploadKind, WriteKind writeKind) throws Exception { 10302f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes int n = 512*1024; 104b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson MockWebServer server = new MockWebServer(); 105b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson server.setBodyLimit(0); 106b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson server.enqueue(new MockResponse()); 107b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson server.play(); 108b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 109b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson HttpURLConnection conn = (HttpURLConnection) server.getUrl("/").openConnection(); 11002f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes conn.setDoOutput(true); 11102f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes conn.setRequestMethod("POST"); 11202f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes if (uploadKind == UploadKind.CHUNKED) { 11302f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes conn.setChunkedStreamingMode(-1); 11402f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } else { 11502f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes conn.setFixedLengthStreamingMode(n); 11602f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } 11702f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes OutputStream out = conn.getOutputStream(); 11802f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes if (writeKind == WriteKind.BYTE_BY_BYTE) { 11902f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes for (int i = 0; i < n; ++i) { 12002f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes out.write('x'); 12102f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } 12202f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } else { 12302f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64*1024]; 12402f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes Arrays.fill(buf, (byte) 'x'); 12502f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes for (int i = 0; i < n; i += buf.length) { 12602f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes out.write(buf, 0, Math.min(buf.length, n - i)); 12702f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } 12802f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } 12902f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes out.close(); 1304cb7f05dc68abb23ae54a5891c369062185f2210Elliott Hughes assertEquals(200, conn.getResponseCode()); 131b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson RecordedRequest request = server.takeRequest(); 132b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson assertEquals(n, request.getBodySize()); 133b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson if (uploadKind == UploadKind.CHUNKED) { 134b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson assertTrue(request.getChunkSizes().size() > 0); 135b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } else { 136b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson assertTrue(request.getChunkSizes().isEmpty()); 137b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 13802f0cb2eb84a112fcf644d7d1fd0b5f94ea2f03bElliott Hughes } 1396247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes 1406247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes public void test_responseCaching() throws Exception { 1416247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes // Test each documented HTTP/1.1 code, plus the first unused value in each range. 1426247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 1436247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes 1446247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes // We can't test 100 because it's not really a response. 1456247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes // assertCached(false, 100); 1466247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, 101); 1476247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, 102); 1486247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(true, 200); 1496247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, 201); 1506247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, 202); 1516247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(true, 203); 1526247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, 204); 1536247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, 205); 1546247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(true, 206); 1556247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, 207); 1566247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(true, 301); 1576247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes for (int i = 302; i <= 308; ++i) { 1586247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, i); 1596247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes } 1606247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes for (int i = 400; i <= 406; ++i) { 1616247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, i); 1626247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes } 1636247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes // (See test_responseCaching_407.) 1646247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, 408); 1656247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, 409); 1666247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(true, 410); 1676247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes for (int i = 411; i <= 418; ++i) { 1686247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, i); 1696247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes } 1706247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes for (int i = 500; i <= 506; ++i) { 1716247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, i); 1726247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes } 1736247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes } 1746247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes 1756247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes public void test_responseCaching_407() throws Exception { 1766247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes // This test will fail on Android because we throw if we're not using a proxy. 1776247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes // This isn't true of the RI, but it seems like useful debugging behavior. 1786247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertCached(false, 407); 1796247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes } 1806247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes 1816247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes private void assertCached(boolean shouldPut, int responseCode) throws Exception { 1826247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes class MyResponseCache extends ResponseCache { 1836247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes public boolean didPut; 1846247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes public CacheResponse get(URI uri, String requestMethod, 1856247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes Map<String, List<String>> requestHeaders) throws IOException { 1866247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes return null; 1876247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes } 1886247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes public CacheRequest put(URI uri, URLConnection conn) throws IOException { 1896247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes didPut = true; 1906247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes return null; 1916247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes } 192b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson } 193b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson MockWebServer server = new MockWebServer(); 194b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson server.enqueue(new MockResponse() 195b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson .setResponseCode(responseCode) 196b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson .addHeader("WWW-Authenticate: challenge")); 197b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson server.play(); 198b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson 1996247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes MyResponseCache cache = new MyResponseCache(); 2006247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes ResponseCache.setDefault(cache); 201b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson HttpURLConnection conn = (HttpURLConnection) server.getUrl("/").openConnection(); 2026247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertEquals(responseCode, conn.getResponseCode()); 2036247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes assertEquals(Integer.toString(responseCode), shouldPut, cache.didPut); 2046247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes 2056247987eb505a482a67f5f19678260d9e7240a5fElliott Hughes } 206e40c9e3935a5024c0f3ebfb3f1441fcd5c48ed86Elliott Hughes} 207