/* * Licensed 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.esigate.test.cases; import junit.framework.TestCase; import org.apache.commons.lang3.StringUtils; import com.meterware.httpunit.AuthorizationRequiredException; import com.meterware.httpunit.GetMethodWebRequest; import com.meterware.httpunit.WebConversation; import com.meterware.httpunit.WebRequest; import com.meterware.httpunit.WebResponse; /** * Response headers forwarding or discarding tests. Standard HTTP headers are defined in: * <ul> * <li><a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">RFC 2616</a></li> * <li><a href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817</a></li> * </ul> * * @author Francois-Xavier Bonnet */ public class ResponseHeadersTest extends TestCase { private final static String APPLICATION_PATH = "http://localhost:8080/esigate-app-aggregator/"; private void assertHeaderDiscarded(String name) throws Exception { assertHeaderDiscarded(name, "dummy"); } private void assertHeaderDiscarded(String name, String value) throws Exception { String resp = sendRequestAndExpectResponseHeader(name, value); assertEquals("HTTP header " + name + " should not be forwarded", "", resp); } private void assertHeaderForwarded(String name) throws Exception { assertHeaderForwarded(name, "dummy"); } private void assertHeaderForwarded(String name, String value) throws Exception { String resp = sendRequestAndExpectResponseHeader(name, value); if (resp.contains("\n")) { // In case of multiple headers String[] responses = StringUtils.split(resp, "\n"); boolean found = false; for (String r : responses) { if (value.equals(r)) { found = true; } } assertTrue("HTTP header " + name + " should be forwarded (multiple values)", found); } else { assertEquals("HTTP header " + name + " should be forwarded", value, resp); } } /** * Location header should be rewritten * * @throws Exception */ private void assertUriInHeaderIsRewritten(String name) throws Exception { String resp = sendRequestAndExpectResponseHeader(name, APPLICATION_PATH.replaceFirst("aggregator", "aggregated1") + "dummy"); assertEquals(name + " header should be rewritten ('aggregator' replaced with 'aggregated1')", APPLICATION_PATH + "nocache/ag1/dummy", resp); } private String sendRequestAndExpectResponseHeader(String name, String value) throws Exception { WebConversation webConversation = new WebConversation(); WebRequest req = new GetMethodWebRequest(APPLICATION_PATH + "nocache/ag1/response-headers.jsp"); req.setHeaderField("X-response-header-" + name, value); WebResponse resp = webConversation.getResponse(req); String[] responseHeader = resp.getHeaderFields(name); if (responseHeader == null || responseHeader.length == 0) { return ""; } String result = responseHeader[0]; for (int i = 1; i < responseHeader.length; i++) { result += "\n" + responseHeader[i]; } return result; } public void testAllow() throws Exception { assertHeaderForwarded("Allow"); } public void testCacheControl() throws Exception { assertHeaderForwarded("Cache-Control"); } public void testContentDisposition() throws Exception { assertHeaderForwarded("Content-Disposition"); } /** * Content-encoding should not be forwarded as EsiGate will decompress gzip responses * */ public void testContentEncoding() { // FIXME not easy to test as adding this header without really gzipping // response body makes the response invalid // assertHeaderDiscarded("Content-Encoding", "gzip"); } public void testContentLanguage() throws Exception { assertHeaderForwarded("Content-Language"); } /** * Keep-alive is managed by the servlet container, we must not try to set it * */ public void testContentLength() { // Cannot test it } /** * Content-Location header should be rewritten * * @throws Exception */ public void testContentLocation() throws Exception { assertUriInHeaderIsRewritten("Content-Location"); } public void testContentMD5() throws Exception { assertHeaderDiscarded("Content-MD5"); } public void testContentRange() throws Exception { assertHeaderForwarded("Content-Range"); } /** * Content-type is often modified by tha application server as it automatically sets it if not defined and it is * case-insensitive so we can have differences depending on the server vendor and version. So we just test that * 'text/plain' was forwarded, no matter the charset. * * @throws Exception */ public void testContentType() throws Exception { // FIXME not easy to test with arbitrary values as application servers // automatically set this header. // String resp = sendRequestAndExpectResponseHeader("Content-Type", // "text/plain"); // if (!StringUtils.startsWithIgnoreCase(resp, "text/plain")) { // fail("HTTP header Content-Type should be forwarded, expected 'text/plain', got '" // + resp + "'"); // } WebConversation webConversation = new WebConversation(); WebRequest req = new GetMethodWebRequest(APPLICATION_PATH + "nocache/ag1/response-headers.jsp"); WebResponse resp = webConversation.getResponse(req); String[] responseHeader = resp.getHeaderFields("Content-type"); if (responseHeader == null || responseHeader.length != 1) { fail("There should be one and only one Content-type header in the response, found " + responseHeader.length); } } public void testDate() throws Exception { // Note: Date header set automatically by most application servers, // cannot override it on Tomcat 7.0 -> problem with cache validation // on Jetty 6 you can override it only with setDateHeader method // setHeader method will not work ! // assertHeaderForwarded("Date", "Fri, 06 Apr 2012 15:18:12 GMT"); WebConversation webConversation = new WebConversation(); WebRequest req = new GetMethodWebRequest(APPLICATION_PATH + "nocache/ag1/response-headers.jsp"); WebResponse resp = webConversation.getResponse(req); String[] responseHeader = resp.getHeaderFields("Date"); if (responseHeader == null || responseHeader.length > 1) { fail("There should be one and only one Date header in the response, found " + responseHeader.length); } } public void testETag() throws Exception { assertHeaderForwarded("E-tag"); } public void testExpires() throws Exception { assertHeaderForwarded("Expires"); } /** * Keep-alive is managed by the servlet container, we must not try to change it * */ public void testKeepAlive() { // Cannot test it } public void testLastModified() throws Exception { assertHeaderForwarded("Last-Modified"); } /** * Link header should be rewritten. * <p> * * Link headers have the following syntax * <p> * <code>Link: </feed>; rel="alternate"</code> * * <p> * See * <ul> * <li> * http://www.esigate.org/mantisbt/view.php?id=129</li> * <li>http://en.wikipedia.org/wiki/List_of_HTTP_header_fields</li> * </ul> * * @throws Exception */ public void testLink() throws Exception { String resp = sendRequestAndExpectResponseHeader("Link", "<" + APPLICATION_PATH.replaceFirst("aggregator", "aggregated1") + "dummy" + ">; rel=\"shortlink\""); // Assert link is rewritten correctly. assertEquals("Link" + " header should be rewritten ('aggregated1' replaced with 'aggregator')", "<" + APPLICATION_PATH + "nocache/ag1/dummy" + ">; rel=\"shortlink\"", resp); } /** * Location header should be rewritten * * @throws Exception */ public void testLocation() throws Exception { assertUriInHeaderIsRewritten("Location"); } /** * P3p header should be forwarded. (Rewritting not implemented yet) * * @throws Exception */ public void testP3p() throws Exception { assertHeaderForwarded("P3P"); } /** * Default ignored, see authentication * * @throws Exception */ public void testProxyAuthenticate() throws Exception { assertHeaderDiscarded("Proxy-Authenticate"); } public void testRefresh() throws Exception { String resp = sendRequestAndExpectResponseHeader("Refresh", "5; url=" + APPLICATION_PATH.replaceFirst("aggregator", "aggregated1") + "dummy"); // Assert link is rewritten correctly. assertEquals("Refresh" + " header should be rewritten ('aggregated1' replaced with 'aggregator')", "5; url=" + APPLICATION_PATH + "nocache/ag1/dummy", resp); } public void testRetryAfter() throws Exception { assertHeaderForwarded("Retry-After"); } public void testServer() throws Exception { assertHeaderForwarded("Server"); } /** * Trailer Ignored (chunked encoding managed by the container) * * @throws Exception */ public void testTrailer() throws Exception { assertHeaderDiscarded("Trailer"); } /** * Transfer-Encoding Ignored (chunked encoding managed by the container). We cannot really test it live as setting * this header generates invalid responses. * */ public void testTransferEncoding() { // Cannot test it } public void testVary() throws Exception { assertHeaderForwarded("Vary"); } public void testVia() throws Exception { // HttpCache adds its own Via header but according to RFC2616 sec 4.2 it // is valid to append a new header instead of combining it with the // existing header. String resp = sendRequestAndExpectResponseHeader("Via", "1.1 EsiGate"); if (!StringUtils.startsWithIgnoreCase(resp, "1.1 EsiGate")) { fail("HTTP header Via should be forwarded, expected 'Via: 1.1 EsiGate...', got '" + resp + "'"); } } public void testWarning() throws Exception { assertHeaderForwarded("Warning"); } public void testWWWAuthenticate() throws Exception { try { assertHeaderForwarded("WWW-Authenticate"); } catch (AuthorizationRequiredException e) { // What we expected return; } fail("Header WWW-Authenticate should be forwarded"); } public void testXPoweredBy() throws Exception { assertHeaderForwarded("X-Powered-By"); } }