/** * Run REST validation on API, stores results into apiEntry. * * @param apiEntry informations about API. */ private void validateTree(ApiEntry apiEntry) { RestValidator restValidator = new RestValidator(apiEntry.getResourceNodes()); String validation = ""; if (restValidator.validateApi()) { if (apiEntry.getQuestionnaires().evaluate().equals("")) { validation = "</p><h3>Great Job! The API is RESTful!</h3><p>"; } validation = validation + "</p><h4>Automatically checked constraints</h4><p>Cache constraint OK!<br />Layered System constraint OK!<br />Uniform Interface OK! (for resources under the API structure)</p>"; } else { ErrsAndWarns errsAndWarns = countErrsAndWarns(apiEntry); if (errsAndWarns.getErrorsCount() == 0) { validation = "</p><h4>Automatically checked constraints</h4><p>Cache constraint OK!<br />Layered System constraint OK!<br />Uniform Interface OK! (for resources under the API structure)<br /> But found " + errsAndWarns.getWarnsCount() + " warnings - see yellow marks below for details.</p>"; } else { validation = "</p><h4>Automatically checked constraints</h4><p>Your API is not RESTful, found " + errsAndWarns.getErrorsCount() + " errors and " + errsAndWarns.getWarnsCount() + " warnings - see colored marks below for details.</p>"; } } apiEntry.setMessage(validation); }
/** * Runs REST validation on API, stores XML results into apiEntry. * * @param apiEntry informations about API. */ private void validateTreeXML(ApiEntry apiEntry) { StringBuilder sb = new StringBuilder(apiEntry.getMessage()); sb.append("<results_of_REST_checking>"); RestValidator restValidator = new RestValidator(apiEntry.getResourceNodes()); String validation; if (restValidator.validateApi()) { validation = "Your API is RESTful."; } else { ErrsAndWarns errsAndWarns = countErrsAndWarns(apiEntry); if (errsAndWarns.getErrorsCount() == 0) { validation = "Your API is RESTful, but found " + errsAndWarns.getWarnsCount() + " warnings - see details of each resource."; } else { validation = "Your API is not RESTful, found " + errsAndWarns.getErrorsCount() + " errors and " + errsAndWarns.getWarnsCount() + " warnings - see details of each resource."; } } sb.append(validation); sb.append("</results_of_REST_checking>"); apiEntry.setMessage(sb.toString()); }
/** * Generates view of HTTP responses of resources in apiEntry and stores it into it. * * @param apiEntry with information about resource tree. */ private void generateViewOfResources(ApiEntry apiEntry) { StringBuilder sb = new StringBuilder(apiEntry.getMessage()); Set<String> visited = new HashSet<String>(); sb.append("\n<div id=\"resourceView\">"); writeResourceNodeView(apiEntry.getResourceNodes(), apiEntry.getBaseUrl(), sb, visited); sb.append("</div>\n"); apiEntry.setMessage(sb.toString()); }
/** * Generates the XML view of API tree from apiEntry and saves it to it. * * @param apiEntry Informations about tree. */ private void generateViewOfTreeXML(ApiEntry apiEntry) { StringBuilder sb = new StringBuilder(apiEntry.getMessage()); sb.append("<api_tree>\n"); writeResourceNodeTreeViewXML( apiEntry.getResourceNodes(), apiEntry.getBaseUrl(), apiEntry.getMaxSiblings(), sb); sb.append("</api_tree>\n"); apiEntry.setMessage(sb.toString()); }
/** * Generates the View of APIs resource tree. * * @param apiEntry for which the view is generated and stored into. */ private void generateViewOfTree(ApiEntry apiEntry) { StringBuilder sb = new StringBuilder(apiEntry.getMessage()); sb.append("\n<div id=\"apiTree\">"); sb.append("<h4>The API structure:</h4> "); sb.append("<ul class=\"short\">\n"); writeResourceNodeTreeView( apiEntry.getResourceNodes(), apiEntry.getBaseUrl(), apiEntry.getMaxSiblings(), sb); sb.append("</ul>\n</div><p>"); apiEntry.setMessage(sb.toString()); }
/** * Handles POST requests for / * * @param apiEntry the object with form data about entry point. * @param result BindingResult with data about validation of input data. * @return JSP page which will be shown. */ @RequestMapping(value = "/", method = RequestMethod.POST) public String doCheckAPI(ApiEntry apiEntry, BindingResult result) { if (result.hasErrors()) { return JSP; } if (!apiEntry.getUrl().startsWith("http://") && !apiEntry.getUrl().startsWith("https://")) { apiEntry.setUrl("http://" + apiEntry.getUrl()); } if (apiEntry.getUrl().length() < 10) { apiEntry.setMessage("Invalid URL"); return JSP; } createTree(apiEntry); if (HttpValidator.responseOk(apiEntry) && apiEntry.getResourceNodes().getDescendants().size() > 0) { validateTree(apiEntry); generateViewOfQuestionnaires(apiEntry); generateViewOfResources(apiEntry); generateViewOfTree(apiEntry); return JSPOkResponse; } else { return JSPBadResponse; } }
/** * Creates API tree from entryPoint specified in apiEntry. Stores the tree into apiEntry. * * @param apiEntry information about API entry point. */ private void createTree(ApiEntry apiEntry) { Map<String, ResourceNode> discoveredResources = new HashMap<String, ResourceNode>(); Queue<ResourceNode> toVisit = new LinkedList<ResourceNode>(); ResourceNode currentResourceNode = new ResourceNode((RemoteResource) apiEntry); apiEntry.setResourceNodes(currentResourceNode); while (currentResourceNode != null) { doCrawle(currentResourceNode, apiEntry, discoveredResources, toVisit); currentResourceNode = toVisit.poll(); } }
/** * Generates view of questionnaire for JSP and stores it into apiEntry. * * @param apiEntry for which the view is generated. */ private void generateViewOfQuestionnaires(ApiEntry apiEntry) { String evaluation = apiEntry.getQuestionnaires().evaluate(); StringBuilder sb = new StringBuilder(apiEntry.getMessage()); sb.append("\n<div id=\"questionnairesEvaluation\">"); sb.append("<h4>Questionnaires evaluation</h4>"); if (evaluation.equals("")) { sb.append("Client-Server OK!<br /> Stateless constraints OK!<br />"); } else { if (!evaluation.contains("This is violation of REST constraint: Client-Server.")) { sb.append("Client-Server constraint OK!<br />"); } else { sb.append("Client-Server constraint VIOLATION!<br />"); } if (!evaluation.contains("This is violation of REST constraint: Stateless.")) { sb.append("Stateless constraint OK!<br />"); } else { sb.append("Stateless constraint VIOLATION!<br />"); } sb.append(evaluation); } sb.append("</div>\n"); apiEntry.setMessage(sb.toString()); }
/** * Appends XML information about entrypoint into entrypoints message. * * @param apiEntry data about entrypoint. */ private void generateViewOfEntryPointXML(ApiEntry apiEntry) { StringBuilder sb = new StringBuilder(apiEntry.getMessage()); sb.append("<test_definition>"); sb.append("<request>"); sb.append("<API_entry_URL>"); sb.append(apiEntry.getUrl()); sb.append("</API_entry_URL>"); if (apiEntry.getRequestHeaders().size() > 0) { sb.append("<headers>"); for (Header header : apiEntry.getRequestHeaders()) { sb.append("<header>"); sb.append("<key>"); sb.append(header.getHeaderKey()); sb.append("</key>"); sb.append("<value>"); sb.append(header.getHeaderValue()); sb.append("</value>"); sb.append("</header>"); } sb.append("</headers>"); } sb.append("</request>"); sb.append("<limits>"); sb.append("<max_siblings>"); sb.append(apiEntry.getMaxSiblings()); sb.append("</max_siblings>"); sb.append("<base_url>"); sb.append(apiEntry.getBaseUrl()); sb.append("</base_url>"); sb.append("</limits>"); sb.append("</test_definition>"); apiEntry.setMessage(sb.toString()); }
/** * Discovers and crawls in currentResourceNode for aditional resources. * * @param currentResourceNode resource which will be crawled. * @param apiEntry Informations about api entrypoint. * @param discoveredResources Map which holds informations about already crawled resources. * @param toVisit queue used to store discovered resources for aditional crawling. */ private void doCrawle( ResourceNode currentResourceNode, ApiEntry apiEntry, Map<String, ResourceNode> discoveredResources, Queue<ResourceNode> toVisit) { if (discoveredResources.containsKey(currentResourceNode.getCurrentResource().getUrl())) { currentResourceNode.setCurrentResource( (RemoteResource) discoveredResources .get(currentResourceNode.getCurrentResource().getUrl()) .getCurrentResource()); currentResourceNode.setCurrentResourceOptions( (RemoteResource) discoveredResources .get(currentResourceNode.getCurrentResource().getUrl()) .getCurrentResourceOptions()); return; } currentResourceNode.sendOptions(); currentResourceNode.sendRequest(); LinkExtrator links = new LinkExtrator(); if (currentResourceNode.getCurrentResource().getResponseBody() == null) { return; } List<String> urls = UrlWorker.getUrls( currentResourceNode.getCurrentResource().getUrl(), links.grabHTMLLinks(currentResourceNode.getCurrentResource().getResponseBody())); List<String> validUrls = new ArrayList<String>(); for (String url : urls) { if (url.startsWith(apiEntry.getBaseUrl())) { validUrls.add(url); } } if (validUrls.size() > 0) { RemoteResource nextResource; int maxResourcesToLoad = apiEntry.getMaxSiblings(); for (String url : validUrls) { try { nextResource = (RemoteResource) currentResourceNode.getCurrentResource().clone(); nextResource.deletePreviousResponse(); if (apiEntry.getUrl().contains("?") && !url.contains("?")) { String append = currentResourceNode.getCurrentResource().getUrl(); append = currentResourceNode .getCurrentResource() .getUrl() .substring(currentResourceNode.getCurrentResource().getUrl().lastIndexOf('?')); nextResource.setUrl(url + append); } else { nextResource.setUrl(url); } ResourceNode nextResourceNode = new ResourceNode(nextResource); currentResourceNode.getDescendants().add(nextResourceNode); if (--maxResourcesToLoad < 0) { // No more crawling for this resource's childs. } else { toVisit.add(nextResourceNode); } } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } discoveredResources.put(currentResourceNode.getCurrentResource().getUrl(), currentResourceNode); }
/** * Counts REST errors and warnings in tree taken from apiEntry. * * @param apiEntry apiEntry information. * @return ErrsAndWarns object with counts. */ private ErrsAndWarns countErrsAndWarns(ApiEntry apiEntry) { ErrsAndWarns errsAndWarns = new ErrsAndWarns(); countErrsAndWarns(apiEntry.getResourceNodes(), errsAndWarns); return errsAndWarns; }
/** * Handles POST requests for /repeatedtesting * * @param apiEntry the object with form data about entry point. * @return ResponseEntity with resource body. */ @RequestMapping(value = "/repeatedtesting", method = RequestMethod.POST) public ResponseEntity<String> doCheckAPIRepeatedTesting(ApiEntry apiEntry, BindingResult result) { if (result.hasErrors()) { apiEntry.setMessage("Invalid input: max siblings"); return new ResponseEntity<String>(apiEntry.getMessage(), HttpStatus.BAD_REQUEST); } if (!apiEntry.getUrl().startsWith("http://") && !apiEntry.getUrl().startsWith("https://")) { apiEntry.setUrl("http://" + apiEntry.getUrl()); } if (apiEntry.getUrl().length() < 10) { apiEntry.setMessage("Invalid input: URL"); return new ResponseEntity<String>(apiEntry.getMessage(), HttpStatus.BAD_REQUEST); } createTree(apiEntry); apiEntry.setMessage("<RESTfulChecker_report>"); generateViewOfEntryPointXML(apiEntry); validateTreeXML(apiEntry); generateViewOfTreeXML(apiEntry); HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.setContentType(MediaType.TEXT_XML); apiEntry.setMessage(apiEntry.getMessage() + "</RESTfulChecker_report>"); return new ResponseEntity<String>(apiEntry.getMessage(), responseHeaders, HttpStatus.OK); }