package com.anjlab.ping.services; import static com.anjlab.ping.services.Utils.getHttpCodeDescription; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.net.URL; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.anjlab.ping.entities.Job; import com.anjlab.ping.entities.JobResult; import com.anjlab.tapestry5.Utils; import com.google.appengine.api.urlfetch.HTTPHeader; import com.google.appengine.api.urlfetch.HTTPMethod; import com.google.appengine.api.urlfetch.HTTPRequest; import com.google.appengine.api.urlfetch.HTTPResponse; import com.google.appengine.api.urlfetch.URLFetchService; import com.google.appengine.api.urlfetch.URLFetchServiceFactory; import com.google.apphosting.api.ApiProxy.ApiDeadlineExceededException; public class JobExecutor { protected static final URLFetchService urlFetchService = URLFetchServiceFactory.getURLFetchService(); protected static void addFirefoxDefaultHeaders(HTTPRequest request) { request.addHeader(new HTTPHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.0; ru; rv:1.9.1.1) Gecko/20090715 Firefox/3.5.1 (.NET CLR 3.5.30729)")); request.addHeader(new HTTPHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")); } private static final Logger logger = LoggerFactory.getLogger(JobExecutor.class); public JobResult execute(Job job) { JobResult jobResult = new JobResult(); jobResult.setTimestamp(new Date()); long startTime = jobResult.getTimestamp().getTime(); String url = job.getPingURL(); try { job.setLastPingTimestamp(new Date()); HTTPRequest request = new HTTPRequest(new URL(url), HTTPMethod.GET); request.getFetchOptions().setDeadline(25d); request.getFetchOptions().doNotFollowRedirects(); request.getFetchOptions().allowTruncate(); addFirefoxDefaultHeaders(request); HTTPResponse response = urlFetchService.fetch(request); jobResult.setHTTPResponseCode(response.getResponseCode()); job.setLastPingResult(0); StringBuffer sb = new StringBuffer(); if (job.isUsesValidatingHttpCode()) { try { StringBuffer hcvb = new StringBuffer(); checkHttpCodeValidation(job, response, hcvb); sb.append(hcvb); } catch (Exception e) { logger.warn("Error checking http code validation for url " + url, e); sb.append("Error checking http code validation for url " + url + ": " + e.getMessage() + "\n"); job.setLastPingResult(job.getLastPingResult() | Job.PING_RESULT_HTTP_ERROR); } } else { sb.append("HTTP code validator wasn't configured for the job\n"); } if (sb.length() > 0) { sb.append("\n"); } if (job.isUsesValidatingRegexp()) { try { StringBuffer rvb = new StringBuffer(); checkRegexpValidation(job, response, rvb); sb.append(rvb); } catch (Exception e) { logger.warn("Error checking regexp validation for url " + url, e); sb.append("Error checking regexp validation for url " + url + ": " + e.getMessage()); job.setLastPingResult(job.getLastPingResult() | Job.PING_RESULT_REGEXP_VALIDATION_FAILED); } } else { sb.append("Regexp validator wasn't configured for the job"); } if (job.getLastPingResult() == 0) { job.setLastPingResult(Job.PING_RESULT_OK); } job.setLastPingDetails(sb.toString()); } catch (IOException e) { logger.debug("Error fetching url " + url, e); processException(job, url, e); } catch (ApiDeadlineExceededException e) { logger.debug("Error fetching url " + url, e); processException(job, url, e); } catch (Exception e) { logger.error("Error fetching url " + url, e); processException(job, url, e); } finally { long endTime = new Date().getTime(); jobResult.setResponseTime((int)(endTime - startTime)); } jobResult.setFailed(job.isLastPingFailed()); jobResult.setPingResult(job.getLastPingResult()); return jobResult; } private void processException(Job job, String url, Exception e) { job.setLastPingResult(Job.PING_RESULT_CONNECTIVITY_PROBLEM); ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream out = new PrintStream(baos); e.printStackTrace(out); String trace = new String(baos.toByteArray()); int endIndex = trace.indexOf("at " + Application.APP_PACKAGE); job.setLastPingDetails("Error fetching url " + url + ": " + (endIndex < 0 ? trace : trace.substring(0, endIndex))); } private void checkHttpCodeValidation(Job job, HTTPResponse response, StringBuffer sb) { int responseCode = response.getResponseCode(); int validatingCode = job.getValidatingHttpCode(); if (validatingCode < 0) { validatingCode = Math.abs(validatingCode / 100); responseCode = Math.abs(responseCode / 100); } sb.append("HTTP response code validation "); if (responseCode != validatingCode) { job.setLastPingResult(job.getLastPingResult() | Job.PING_RESULT_HTTP_ERROR); sb.append("failed"); } else { sb.append("succeeded"); } sb.append(": expected "); sb.append(getHttpCodeDescription(job.getValidatingHttpCode())); sb.append(", was "); sb.append(response.getResponseCode()); sb.append("\n\nHTTP headers:\n\n"); for (int i = 0; i < response.getHeaders().size(); i++) { HTTPHeader httpHeader = response.getHeaders().get(i); sb.append(httpHeader.getName()); sb.append('='); sb.append(httpHeader.getValue()); sb.append('\n'); } } private void checkRegexpValidation(Job job, HTTPResponse response, StringBuffer sb) throws UnsupportedEncodingException { String content = new String(response.getContent(), Utils.isNullOrEmpty(job.getResponseEncoding()) ? "UTF-8" : job.getResponseEncoding()); sb.append("Regexp validation "); String regexp = job.getValidatingRegexp(); if (!Utils.isNullOrEmpty(regexp)) { Matcher m = Pattern.compile(regexp).matcher(content); if (m.find()) { sb.append("succeeded"); } else { job.setLastPingResult(job.getLastPingResult() | Job.PING_RESULT_REGEXP_VALIDATION_FAILED); sb.append("failed"); } } else { sb.append("unknown (regexp was not specified)"); } sb.append("\n\nResponse content is:\n\n"); sb.append(content); } }