Code Monkey home page Code Monkey logo

Comments (8)

AmrEldib avatar AmrEldib commented on August 28, 2024

I've been working with @juicetiger1 on this issue.

Here are my testing notes if it helps address the issue:
Issue: Resource Proxy (Java) fails to Renew Token after it Expires

Notes:

<serverUrl url="https://citymaps.peterborough.ca/arcgis/rest/services/PtboLocatorLocateApp/GeocodeServer"
matchAll="true"
tokenServiceUri="https://citymaps.peterborough.ca/portal/sharing/rest/generateToken"
username="XXXXX"
password="XXXXX"
/>
  • ArcGIS Server is federated with Portal. Both Server and Portal are at 10.6.1

  • The resource proxy is hosted on Tomcat web server.

  • The proxy is able to successfully generate a token and use it without a problem.

  • After an hour passes, the token expires, and the resource proxy fails to recognize that the token is expired, and doesn't attempt to generate a new token.

  • The proxy log shows no errors

  • We inspected the Java code responsible for renewing the token

  • After adding a line to print log of the response from Server that includes any error messages, we found that the proxy prints some incoherent text instead of a proper string response.

  • Following the logic of the code in line 212, this string won't match any of the conditions of the If statement and won't be able to recognize the error returned from the server/portal
    https://github.com/Esri/resource-proxy/blob/master/Java/proxy.jsp#L212

  • The issue seems limited to this one installation of the resource proxy

  • Client has other applications setup with their own resource proxy (also, Java on Tomcat) and they all work fine with similar configuration.

  • I've attempted to reproduce the issue, but couldn't. The proxy works fine.

  • We're looking for help identifying what's causing the resource proxy to behave this way.

from resource-proxy.

cpore avatar cpore commented on August 28, 2024

@juicetiger1, @AmrEldib,

I've run into what may be the same issue. If so, I've managed to fix/workaround it. I was getting the same "wing-ding" response body when one particular service sent along an authentication error that looked like this when written to the log:
image

I actually found two issues when handling the authentication error, but the root cause is that the authentication error is gzip encoded. Now, I didn't dig deep enough to determine if this encoding issue is with the proxy code, something else in the call stack (like a tomcat, or connection issue), or if the problem was with the server itself sending an ill-formed response (it would be nice to know why other responses aren't gzipped, or if they are, why they get properly decoded and this one doesn't). But either way I was able to put in a work-around to gzip decode the response if other means didn't catch the authentication error, like so:

    //proxy gets the response back from server
    private boolean fetchAndPassBackToClient(HttpURLConnection con, HttpServletResponse clientResponse, boolean ignoreAuthenticationErrors) throws IOException{
        if (con!=null){
            //copy the response content to the response to the client
            InputStream byteStream;
            if (con.getResponseCode() >= 400 && con.getErrorStream() != null){
                if (ignoreAuthenticationErrors && (con.getResponseCode() == 498 || con.getResponseCode() == 499)){
                    log.info("Authentication error found in response code.");
                    return true;
                }
                byteStream = con.getErrorStream();
            }else{
                byteStream = con.getInputStream();
            }

            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int length = 5000;
            byte[] bytes = new byte[length];
            int bytesRead;

            while ((bytesRead = byteStream.read(bytes, 0, length)) > 0) {
                buffer.write(bytes, 0, bytesRead);
            }

            //if the content of the HttpURLConnection contains error message, it means the token expired, so let proxy try again
            //only check small responses (< 1KB). It's probably expensive to convert and parse large responses every time.
            log.info("RESPONSE BUFFER SIZE: " + buffer.size());
            if(buffer.size() <= 1024 && !ignoreAuthenticationErrors){
                String strResponse = buffer.toString();
                log.info("RESPONSE: " + strResponse);
                if (hasResponseBodyAuthenticationError(strResponse)) {
                    log.info("Authentication error found in response body.");
                    return true;
                }

                //Do a gzip decode and check those results as well
                if("gzip".equals(con.getHeaderField("Content-Encoding"))) {
                    GZIPInputStream gzin = new GZIPInputStream(new ByteArrayInputStream(buffer.toByteArray()));
                    ByteArrayOutputStream outputBytes = new ByteArrayOutputStream();
                    int read = 0;
                    byte gzBytes[] = new byte[1024];

                    while ((read = gzin.read(gzBytes, 0, gzBytes.length)) > 0) {
                        outputBytes.write(gzBytes, 0, read);
                    }

                    String strGzipResponse = outputBytes.toString();
                    log.info("CONTAINS GZIP HEADER. UN-GZIPPED RESPONSE: " + strGzipResponse);
                    if (hasResponseBodyAuthenticationError(strGzipResponse)) {
                        log.info("Authentication error found in gzipped response body.");
                        return true;
                    }
                }
            }

            copyHeadersToResponse(con, clientResponse);

            clientResponse.setStatus(con.getResponseCode());

            byte[] byteResponse = buffer.toByteArray();
            OutputStream ostream = clientResponse.getOutputStream();
            ostream.write(byteResponse);
        }
        return false;
    }

Since I'm unable to determine exactly what conditions cause the decoding issue, I just check all responses for errors that aren't caught via the first check by gzip decoding them, and optimize to only check small-ish responses to save some cycles.

You also may notice that I moved the code that sets the response headers to occur after the authentication error check. I found that when it's before the check, it adds duplicate headers that cause issues at the client since that section of code would be called twice if an authentication error occurred.

Finally, you may notice I removed all the flush() and close() calls, as those are unnecessary in Java 8, so be sure to leave those in if using something lower.

Anywho, I hope this helps you (or anybody) out. Apologies, I can't submit a pull request for a fix because I've refactored this project into a Spring Boot project to make it more easily deployable in a Kubernetes cluster.

from resource-proxy.

cpore avatar cpore commented on August 28, 2024

On another, slightly related note, I've also found that some error responses have been missed because they contain a space before and after the colon and should be checked, like so:

private boolean hasResponseBodyAuthenticationError(String responseBody){
        return responseBody.contains("error")
               && (responseBody.contains("\"code\": 498")
                   || responseBody.contains("\"code\": 499")
                   || responseBody.contains("\"code\":498")
                   || responseBody.contains("\"code\":499")
                   || responseBody.contains("\"code\" : 498") //add this check
                   || responseBody.contains("\"code\" : 499")); // and this check
    }

from resource-proxy.

juicetiger1 avatar juicetiger1 commented on August 28, 2024

Are you able to send the copyHeadersToResponse function as well?

Thanks

from resource-proxy.

cpore avatar cpore commented on August 28, 2024

@juicetiger1

Are you able to send the copyHeadersToResponse function as well?

Thanks

I've made no changes here, other than re-arranging the code. I simply extracted all of the header-setting code at the top of the method into its own method and moved it below the authentication checking code.

from resource-proxy.

github-actions avatar github-actions commented on August 28, 2024

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you need additional assistance please contact Esri Technical Support. Thank you for your contributions.

from resource-proxy.

github-actions avatar github-actions commented on August 28, 2024

This issue has been automatically closed due to inactivity. If you need additional assistance please contact Esri Technical Support.

from resource-proxy.

ranga-tolapi avatar ranga-tolapi commented on August 28, 2024

Are you able to send the copyHeadersToResponse function as well?

Thanks

private void copyHeadersToResponse(HttpURLConnection con, HttpServletResponse clientResponse){
	Map<String, List<String>> headerFields = con.getHeaderFields();
	Set<String> headerFieldsSet = headerFields.keySet();

	//copy the response header to the response to the client
	for (String headerFieldKey : headerFieldsSet){
		//prevent request for partial content
		if (headerFieldKey != null && headerFieldKey.toLowerCase().equals("accept-ranges")){
			continue;
		}

		List<String> headerFieldValue = headerFields.get(headerFieldKey);
		StringBuilder sb = new StringBuilder();
		for (String value : headerFieldValue) {
			// Reset the content-type for OGC WMS - issue #367
			// Note: this might not be what everyone expects, but it helps some users
			// TODO: make this configurable
			if (headerFieldKey != null && headerFieldKey.toLowerCase().equals("content-type")){
				if (value != null && value.toLowerCase().contains("application/vnd.ogc.wms_xml")){
					_log(Level.FINE, "Adjusting Content-Type for WMS OGC: " + value);
					value = "text/xml";
				}
			}

			// remove Transfer-Encoding/chunked to the client
			// StackOverflow http://stackoverflow.com/questions/31452074/how-to-proxy-http-requests-in-spring-mvc
			if (headerFieldKey != null && headerFieldKey.toLowerCase().equals("transfer-encoding")) {
				if (value != null && value.toLowerCase().equals("chunked")) {
					continue;
				}
			}

			sb.append(value);
			sb.append("");
		}
		if (headerFieldKey != null){
			clientResponse.addHeader(headerFieldKey, DataValidUtil.removeCRLF(sb.toString()));
		}
	}
}

from resource-proxy.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.