| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.hadoop.http; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.net.HttpURLConnection; |
| import java.net.InetSocketAddress; |
| import java.net.URL; |
| import java.util.Arrays; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.Executors; |
| |
| import javax.servlet.Filter; |
| import javax.servlet.FilterChain; |
| import javax.servlet.FilterConfig; |
| import javax.servlet.ServletContext; |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletRequest; |
| import javax.servlet.ServletResponse; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletRequestWrapper; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import junit.framework.Assert; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.CommonConfigurationKeys; |
| import org.apache.hadoop.http.HttpServer.QuotingInputFilter.RequestQuoter; |
| import org.apache.hadoop.http.resource.JerseyResource; |
| import org.apache.hadoop.security.Groups; |
| import org.apache.hadoop.security.ShellBasedUnixGroupsMapping; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.apache.hadoop.security.authorize.AccessControlList; |
| import org.junit.AfterClass; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.mockito.Mockito; |
| import org.mortbay.util.ajax.JSON; |
| |
| public class TestHttpServer extends HttpServerFunctionalTest { |
| static final Log LOG = LogFactory.getLog(TestHttpServer.class); |
| private static HttpServer server; |
| private static URL baseUrl; |
| private static final int MAX_THREADS = 10; |
| |
| @SuppressWarnings("serial") |
| public static class EchoMapServlet extends HttpServlet { |
| @SuppressWarnings("unchecked") |
| @Override |
| public void doGet(HttpServletRequest request, |
| HttpServletResponse response |
| ) throws ServletException, IOException { |
| PrintWriter out = response.getWriter(); |
| Map<String, String[]> params = request.getParameterMap(); |
| SortedSet<String> keys = new TreeSet<String>(params.keySet()); |
| for(String key: keys) { |
| out.print(key); |
| out.print(':'); |
| String[] values = params.get(key); |
| if (values.length > 0) { |
| out.print(values[0]); |
| for(int i=1; i < values.length; ++i) { |
| out.print(','); |
| out.print(values[i]); |
| } |
| } |
| out.print('\n'); |
| } |
| out.close(); |
| } |
| } |
| |
| @SuppressWarnings("serial") |
| public static class EchoServlet extends HttpServlet { |
| @SuppressWarnings("unchecked") |
| @Override |
| public void doGet(HttpServletRequest request, |
| HttpServletResponse response |
| ) throws ServletException, IOException { |
| PrintWriter out = response.getWriter(); |
| SortedSet<String> sortedKeys = new TreeSet<String>(); |
| Enumeration<String> keys = request.getParameterNames(); |
| while(keys.hasMoreElements()) { |
| sortedKeys.add(keys.nextElement()); |
| } |
| for(String key: sortedKeys) { |
| out.print(key); |
| out.print(':'); |
| out.print(request.getParameter(key)); |
| out.print('\n'); |
| } |
| out.close(); |
| } |
| } |
| |
| @SuppressWarnings("serial") |
| public static class LongHeaderServlet extends HttpServlet { |
| @SuppressWarnings("unchecked") |
| @Override |
| public void doGet(HttpServletRequest request, |
| HttpServletResponse response |
| ) throws ServletException, IOException { |
| Assert.assertEquals(63 * 1024, request.getHeader("longheader").length()); |
| response.setStatus(HttpServletResponse.SC_OK); |
| } |
| } |
| |
| @SuppressWarnings("serial") |
| public static class HtmlContentServlet extends HttpServlet { |
| @Override |
| public void doGet(HttpServletRequest request, |
| HttpServletResponse response |
| ) throws ServletException, IOException { |
| response.setContentType("text/html"); |
| PrintWriter out = response.getWriter(); |
| out.print("hello world"); |
| out.close(); |
| } |
| } |
| |
| @BeforeClass public static void setup() throws Exception { |
| Configuration conf = new Configuration(); |
| conf.setInt(HttpServer.HTTP_MAX_THREADS, 10); |
| server = createTestServer(conf); |
| server.addServlet("echo", "/echo", EchoServlet.class); |
| server.addServlet("echomap", "/echomap", EchoMapServlet.class); |
| server.addServlet("htmlcontent", "/htmlcontent", HtmlContentServlet.class); |
| server.addServlet("longheader", "/longheader", LongHeaderServlet.class); |
| server.addJerseyResourcePackage( |
| JerseyResource.class.getPackage().getName(), "/jersey/*"); |
| server.start(); |
| baseUrl = getServerURL(server); |
| LOG.info("HTTP server started: "+ baseUrl); |
| } |
| |
| @AfterClass public static void cleanup() throws Exception { |
| server.stop(); |
| } |
| |
| /** Test the maximum number of threads cannot be exceeded. */ |
| @Test public void testMaxThreads() throws Exception { |
| int clientThreads = MAX_THREADS * 10; |
| Executor executor = Executors.newFixedThreadPool(clientThreads); |
| // Run many clients to make server reach its maximum number of threads |
| final CountDownLatch ready = new CountDownLatch(clientThreads); |
| final CountDownLatch start = new CountDownLatch(1); |
| for (int i = 0; i < clientThreads; i++) { |
| executor.execute(new Runnable() { |
| @Override |
| public void run() { |
| ready.countDown(); |
| try { |
| start.await(); |
| assertEquals("a:b\nc:d\n", |
| readOutput(new URL(baseUrl, "/echo?a=b&c=d"))); |
| int serverThreads = server.webServer.getThreadPool().getThreads(); |
| assertTrue("More threads are started than expected, Server Threads count: " |
| + serverThreads, serverThreads <= MAX_THREADS); |
| System.out.println("Number of threads = " + serverThreads + |
| " which is less or equal than the max = " + MAX_THREADS); |
| } catch (Exception e) { |
| // do nothing |
| } |
| } |
| }); |
| } |
| // Start the client threads when they are all ready |
| ready.await(); |
| start.countDown(); |
| } |
| |
| @Test public void testEcho() throws Exception { |
| assertEquals("a:b\nc:d\n", |
| readOutput(new URL(baseUrl, "/echo?a=b&c=d"))); |
| assertEquals("a:b\nc<:d\ne:>\n", |
| readOutput(new URL(baseUrl, "/echo?a=b&c<=d&e=>"))); |
| } |
| |
| /** Test the echo map servlet that uses getParameterMap. */ |
| @Test public void testEchoMap() throws Exception { |
| assertEquals("a:b\nc:d\n", |
| readOutput(new URL(baseUrl, "/echomap?a=b&c=d"))); |
| assertEquals("a:b,>\nc<:d\n", |
| readOutput(new URL(baseUrl, "/echomap?a=b&c<=d&a=>"))); |
| } |
| |
| /** |
| * Test that verifies headers can be up to 64K long. |
| * The test adds a 63K header leaving 1K for other headers. |
| * This is because the header buffer setting is for ALL headers, |
| * names and values included. */ |
| @Test public void testLongHeader() throws Exception { |
| URL url = new URL(baseUrl, "/longheader"); |
| HttpURLConnection conn = (HttpURLConnection) url.openConnection(); |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0 ; i < 63 * 1024; i++) { |
| sb.append("a"); |
| } |
| conn.setRequestProperty("longheader", sb.toString()); |
| assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); |
| } |
| |
| @Test public void testContentTypes() throws Exception { |
| // Static CSS files should have text/css |
| URL cssUrl = new URL(baseUrl, "/static/test.css"); |
| HttpURLConnection conn = (HttpURLConnection)cssUrl.openConnection(); |
| conn.connect(); |
| assertEquals(200, conn.getResponseCode()); |
| assertEquals("text/css", conn.getContentType()); |
| |
| // Servlets should have text/plain with proper encoding by default |
| URL servletUrl = new URL(baseUrl, "/echo?a=b"); |
| conn = (HttpURLConnection)servletUrl.openConnection(); |
| conn.connect(); |
| assertEquals(200, conn.getResponseCode()); |
| assertEquals("text/plain; charset=utf-8", conn.getContentType()); |
| |
| // We should ignore parameters for mime types - ie a parameter |
| // ending in .css should not change mime type |
| servletUrl = new URL(baseUrl, "/echo?a=b.css"); |
| conn = (HttpURLConnection)servletUrl.openConnection(); |
| conn.connect(); |
| assertEquals(200, conn.getResponseCode()); |
| assertEquals("text/plain; charset=utf-8", conn.getContentType()); |
| |
| // Servlets that specify text/html should get that content type |
| servletUrl = new URL(baseUrl, "/htmlcontent"); |
| conn = (HttpURLConnection)servletUrl.openConnection(); |
| conn.connect(); |
| assertEquals(200, conn.getResponseCode()); |
| assertEquals("text/html; charset=utf-8", conn.getContentType()); |
| |
| // JSPs should default to text/html with utf8 |
| servletUrl = new URL(baseUrl, "/testjsp.jsp"); |
| conn = (HttpURLConnection)servletUrl.openConnection(); |
| conn.connect(); |
| assertEquals(200, conn.getResponseCode()); |
| assertEquals("text/html; charset=utf-8", conn.getContentType()); |
| } |
| |
| /** |
| * Dummy filter that mimics as an authentication filter. Obtains user identity |
| * from the request parameter user.name. Wraps around the request so that |
| * request.getRemoteUser() returns the user identity. |
| * |
| */ |
| public static class DummyServletFilter implements Filter { |
| @Override |
| public void destroy() { } |
| |
| @Override |
| public void doFilter(ServletRequest request, ServletResponse response, |
| FilterChain filterChain) throws IOException, ServletException { |
| final String userName = request.getParameter("user.name"); |
| ServletRequest requestModified = |
| new HttpServletRequestWrapper((HttpServletRequest) request) { |
| @Override |
| public String getRemoteUser() { |
| return userName; |
| } |
| }; |
| filterChain.doFilter(requestModified, response); |
| } |
| |
| @Override |
| public void init(FilterConfig arg0) throws ServletException { } |
| } |
| |
| /** |
| * FilterInitializer that initialized the DummyFilter. |
| * |
| */ |
| public static class DummyFilterInitializer extends FilterInitializer { |
| public DummyFilterInitializer() { |
| } |
| |
| @Override |
| public void initFilter(FilterContainer container, Configuration conf) { |
| container.addFilter("DummyFilter", DummyServletFilter.class.getName(), null); |
| } |
| } |
| |
| /** |
| * Access a URL and get the corresponding return Http status code. The URL |
| * will be accessed as the passed user, by sending user.name request |
| * parameter. |
| * |
| * @param urlstring |
| * @param userName |
| * @return |
| * @throws IOException |
| */ |
| static int getHttpStatusCode(String urlstring, String userName) |
| throws IOException { |
| URL url = new URL(urlstring + "?user.name=" + userName); |
| System.out.println("Accessing " + url + " as user " + userName); |
| HttpURLConnection connection = (HttpURLConnection)url.openConnection(); |
| connection.connect(); |
| return connection.getResponseCode(); |
| } |
| |
| /** |
| * Custom user->group mapping service. |
| */ |
| public static class MyGroupsProvider extends ShellBasedUnixGroupsMapping { |
| static Map<String, List<String>> mapping = new HashMap<String, List<String>>(); |
| |
| static void clearMapping() { |
| mapping.clear(); |
| } |
| |
| @Override |
| public List<String> getGroups(String user) throws IOException { |
| return mapping.get(user); |
| } |
| } |
| |
| /** |
| * Verify the access for /logs, /stacks, /conf, /logLevel and /metrics |
| * servlets, when authentication filters are set, but authorization is not |
| * enabled. |
| * @throws Exception |
| */ |
| @Test |
| public void testDisabledAuthorizationOfDefaultServlets() throws Exception { |
| |
| Configuration conf = new Configuration(); |
| |
| // Authorization is disabled by default |
| conf.set(HttpServer.FILTER_INITIALIZER_PROPERTY, |
| DummyFilterInitializer.class.getName()); |
| conf.set(CommonConfigurationKeys.HADOOP_SECURITY_GROUP_MAPPING, |
| MyGroupsProvider.class.getName()); |
| Groups.getUserToGroupsMappingService(conf); |
| MyGroupsProvider.clearMapping(); |
| MyGroupsProvider.mapping.put("userA", Arrays.asList("groupA")); |
| MyGroupsProvider.mapping.put("userB", Arrays.asList("groupB")); |
| |
| HttpServer myServer = new HttpServer("test", "0.0.0.0", 0, true, conf); |
| myServer.setAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE, conf); |
| myServer.start(); |
| int port = myServer.getPort(); |
| String serverURL = "http://localhost:" + port + "/"; |
| for (String servlet : new String[] { "conf", "logs", "stacks", |
| "logLevel", "metrics" }) { |
| for (String user : new String[] { "userA", "userB" }) { |
| assertEquals(HttpURLConnection.HTTP_OK, getHttpStatusCode(serverURL |
| + servlet, user)); |
| } |
| } |
| myServer.stop(); |
| } |
| |
| /** |
| * Verify the administrator access for /logs, /stacks, /conf, /logLevel and |
| * /metrics servlets. |
| * |
| * @throws Exception |
| */ |
| @Test |
| public void testAuthorizationOfDefaultServlets() throws Exception { |
| Configuration conf = new Configuration(); |
| conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, |
| true); |
| conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN, |
| true); |
| conf.set(HttpServer.FILTER_INITIALIZER_PROPERTY, |
| DummyFilterInitializer.class.getName()); |
| |
| conf.set(CommonConfigurationKeys.HADOOP_SECURITY_GROUP_MAPPING, |
| MyGroupsProvider.class.getName()); |
| Groups.getUserToGroupsMappingService(conf); |
| MyGroupsProvider.clearMapping(); |
| MyGroupsProvider.mapping.put("userA", Arrays.asList("groupA")); |
| MyGroupsProvider.mapping.put("userB", Arrays.asList("groupB")); |
| MyGroupsProvider.mapping.put("userC", Arrays.asList("groupC")); |
| MyGroupsProvider.mapping.put("userD", Arrays.asList("groupD")); |
| MyGroupsProvider.mapping.put("userE", Arrays.asList("groupE")); |
| |
| HttpServer myServer = new HttpServer("test", "0.0.0.0", 0, true, conf, |
| new AccessControlList("userA,userB groupC,groupD")); |
| myServer.setAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE, conf); |
| myServer.start(); |
| int port = myServer.getPort(); |
| String serverURL = "http://localhost:" + port + "/"; |
| for (String servlet : new String[] { "conf", "logs", "stacks", |
| "logLevel", "metrics" }) { |
| for (String user : new String[] { "userA", "userB", "userC", "userD" }) { |
| assertEquals(HttpURLConnection.HTTP_OK, getHttpStatusCode(serverURL |
| + servlet, user)); |
| } |
| assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, getHttpStatusCode( |
| serverURL + servlet, "userE")); |
| } |
| myServer.stop(); |
| } |
| |
| @Test |
| public void testRequestQuoterWithNull() throws Exception { |
| HttpServletRequest request = Mockito.mock(HttpServletRequest.class); |
| Mockito.doReturn(null).when(request).getParameterValues("dummy"); |
| RequestQuoter requestQuoter = new RequestQuoter(request); |
| String[] parameterValues = requestQuoter.getParameterValues("dummy"); |
| Assert.assertEquals("It should return null " |
| + "when there are no values for the parameter", null, parameterValues); |
| } |
| |
| @Test |
| public void testRequestQuoterWithNotNull() throws Exception { |
| HttpServletRequest request = Mockito.mock(HttpServletRequest.class); |
| String[] values = new String[] { "abc", "def" }; |
| Mockito.doReturn(values).when(request).getParameterValues("dummy"); |
| RequestQuoter requestQuoter = new RequestQuoter(request); |
| String[] parameterValues = requestQuoter.getParameterValues("dummy"); |
| Assert.assertTrue("It should return Parameter Values", Arrays.equals( |
| values, parameterValues)); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static Map<String, Object> parse(String jsonString) { |
| return (Map<String, Object>)JSON.parse(jsonString); |
| } |
| |
| @Test public void testJersey() throws Exception { |
| LOG.info("BEGIN testJersey()"); |
| final String js = readOutput(new URL(baseUrl, "/jersey/foo?op=bar")); |
| final Map<String, Object> m = parse(js); |
| LOG.info("m=" + m); |
| assertEquals("foo", m.get(JerseyResource.PATH)); |
| assertEquals("bar", m.get(JerseyResource.OP)); |
| LOG.info("END testJersey()"); |
| } |
| |
| @Test |
| public void testHasAdministratorAccess() throws Exception { |
| Configuration conf = new Configuration(); |
| conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false); |
| ServletContext context = Mockito.mock(ServletContext.class); |
| Mockito.when(context.getAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE)).thenReturn(conf); |
| Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(null); |
| HttpServletRequest request = Mockito.mock(HttpServletRequest.class); |
| Mockito.when(request.getRemoteUser()).thenReturn(null); |
| HttpServletResponse response = Mockito.mock(HttpServletResponse.class); |
| |
| //authorization OFF |
| Assert.assertTrue(HttpServer.hasAdministratorAccess(context, request, response)); |
| |
| //authorization ON & user NULL |
| response = Mockito.mock(HttpServletResponse.class); |
| conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, true); |
| Assert.assertFalse(HttpServer.hasAdministratorAccess(context, request, response)); |
| Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_UNAUTHORIZED), Mockito.anyString()); |
| |
| //authorization ON & user NOT NULL & ACLs NULL |
| response = Mockito.mock(HttpServletResponse.class); |
| Mockito.when(request.getRemoteUser()).thenReturn("foo"); |
| Assert.assertTrue(HttpServer.hasAdministratorAccess(context, request, response)); |
| |
| //authorization ON & user NOT NULL & ACLs NOT NULL & user not in ACLs |
| response = Mockito.mock(HttpServletResponse.class); |
| AccessControlList acls = Mockito.mock(AccessControlList.class); |
| Mockito.when(acls.isUserAllowed(Mockito.<UserGroupInformation>any())).thenReturn(false); |
| Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(acls); |
| Assert.assertFalse(HttpServer.hasAdministratorAccess(context, request, response)); |
| Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_UNAUTHORIZED), Mockito.anyString()); |
| |
| //authorization ON & user NOT NULL & ACLs NOT NULL & user in in ACLs |
| response = Mockito.mock(HttpServletResponse.class); |
| Mockito.when(acls.isUserAllowed(Mockito.<UserGroupInformation>any())).thenReturn(true); |
| Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(acls); |
| Assert.assertTrue(HttpServer.hasAdministratorAccess(context, request, response)); |
| |
| } |
| |
| @Test |
| public void testRequiresAuthorizationAccess() throws Exception { |
| Configuration conf = new Configuration(); |
| ServletContext context = Mockito.mock(ServletContext.class); |
| Mockito.when(context.getAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE)).thenReturn(conf); |
| HttpServletRequest request = Mockito.mock(HttpServletRequest.class); |
| HttpServletResponse response = Mockito.mock(HttpServletResponse.class); |
| |
| //requires admin access to instrumentation, FALSE by default |
| Assert.assertTrue(HttpServer.isInstrumentationAccessAllowed(context, request, response)); |
| |
| //requires admin access to instrumentation, TRUE |
| conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN, true); |
| conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, true); |
| AccessControlList acls = Mockito.mock(AccessControlList.class); |
| Mockito.when(acls.isUserAllowed(Mockito.<UserGroupInformation>any())).thenReturn(false); |
| Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(acls); |
| Assert.assertFalse(HttpServer.isInstrumentationAccessAllowed(context, request, response)); |
| } |
| |
| @Test public void testBindAddress() throws Exception { |
| checkBindAddress("0.0.0.0", 0, false).stop(); |
| // hang onto this one for a bit more testing |
| HttpServer myServer = checkBindAddress("localhost", 0, false); |
| HttpServer myServer2 = null; |
| try { |
| int port = myServer.getListenerAddress().getPort(); |
| // it's already in use, true = expect a higher port |
| myServer2 = checkBindAddress("localhost", port, true); |
| // try to reuse the port |
| port = myServer2.getListenerAddress().getPort(); |
| myServer2.stop(); |
| assertEquals(-1, myServer2.getPort()); // not bound |
| myServer2.openListener(); |
| assertEquals(port, myServer2.getPort()); // expect same port |
| } finally { |
| myServer.stop(); |
| if (myServer2 != null) { |
| myServer2.stop(); |
| } |
| } |
| } |
| |
| private HttpServer checkBindAddress(String host, int port, boolean findPort) |
| throws Exception { |
| HttpServer server = createServer(host, port); |
| try { |
| // not bound, ephemeral should return requested port (0 for ephemeral) |
| InetSocketAddress addr = server.getListenerAddress(); |
| assertEquals(port, addr.getPort()); |
| // verify hostname is what was given |
| server.openListener(); |
| addr = server.getListenerAddress(); |
| assertEquals(host, addr.getHostName()); |
| |
| int boundPort = addr.getPort(); |
| if (port == 0) { |
| assertTrue(boundPort != 0); // ephemeral should now return bound port |
| } else if (findPort) { |
| assertTrue(boundPort > port); |
| // allow a little wiggle room to prevent random test failures if |
| // some consecutive ports are already in use |
| assertTrue(addr.getPort() - port < 8); |
| } |
| } catch (Exception e) { |
| server.stop(); |
| throw e; |
| } |
| return server; |
| } |
| |
| @Test |
| public void testNoCacheHeader() throws Exception { |
| URL url = new URL(baseUrl, "/echo?a=b&c=d"); |
| HttpURLConnection conn = (HttpURLConnection) url.openConnection(); |
| assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); |
| assertEquals("no-cache", conn.getHeaderField("Cache-Control")); |
| assertEquals("no-cache", conn.getHeaderField("Pragma")); |
| assertNotNull(conn.getHeaderField("Expires")); |
| assertNotNull(conn.getHeaderField("Date")); |
| assertEquals(conn.getHeaderField("Expires"), conn.getHeaderField("Date")); |
| } |
| |
| } |