{"action":"created","comment":{"url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls/comments/60621291","id":60621291,"diff_hunk":"@@ -0,0 +1,230 @@\n+\n+package edu.wpi.grip.core.http;\n+\n+import com.google.common.collect.ImmutableMap;\n+import com.google.gson.Gson;\n+import com.google.gson.GsonBuilder;\n+import com.sun.net.httpserver.HttpServer;\n+\n+import edu.wpi.grip.core.exception.GripException;\n+import edu.wpi.grip.core.exception.GripServerException;\n+import edu.wpi.grip.core.http.GripServer.HttpServerFactory;\n+\n+import java.io.ByteArrayInputStream;\n+import java.io.IOException;\n+import java.net.InetSocketAddress;\n+import java.util.Arrays;\n+import java.util.Map;\n+import java.util.function.Supplier;\n+\n+import edu.wpi.grip.core.serialization.Project;\n+import edu.wpi.grip.core.settings.ProjectSettings;\n+import edu.wpi.grip.core.settings.SettingsProvider;\n+import org.apache.http.HttpResponse;\n+import org.apache.http.HttpVersion;\n+import org.apache.http.client.methods.HttpGet;\n+import org.apache.http.client.methods.HttpPost;\n+import org.apache.http.entity.BasicHttpEntity;\n+import org.apache.http.impl.client.DefaultHttpClient;\n+import org.apache.http.params.CoreProtocolPNames;\n+import org.apache.http.util.EntityUtils;\n+import org.junit.After;\n+import org.junit.Test;\n+import static org.junit.Assert.*;\n+\n+/**\n+ *\n+ */\n+public class GripServerTest {\n+\n+    private static final int GRIP_SERVER_TEST_PORT = 8080;\n+    private final DefaultHttpClient client;\n+    private final TestServerFactory serverFactory;\n+    private GripServer instance;\n+\n+    public static class TestServerFactory implements HttpServerFactory {\n+\n+        private int port;\n+\n+        public int getPort() {\n+            return port;\n+        }\n+\n+        @Override\n+        public HttpServer create(int port) {\n+            final int MAX_TRIES = 200;\n+            IOException lastException = null;\n+            for (int offset = 0; offset < MAX_TRIES; offset++) {\n+                try {\n+                    this.port = port + offset;\n+                    return HttpServer.create(new InetSocketAddress(\"localhost\", this.port), 1);\n+                } catch (IOException e) {\n+                    // That port is taken -- keep trying different ports\n+                    lastException = e;\n+                }\n+            }\n+            throw new AssertionError(\n+                    String.format(\"Could not create a server on port %d after %d attempts\", port, MAX_TRIES),\n+                    lastException);\n+        }\n+\n+    }\n+\n+    /**\n+     * Public factory method for testing.\n+     */\n+    public static GripServer makeServer(HttpServerFactory factory, SettingsProvider settingsProvider, Project project) {\n+        return new GripServer(factory, settingsProvider, project);\n+    }\n+\n+    public GripServerTest() {\n+        ProjectSettings mockSettings = new ProjectSettings();\n+        mockSettings.setServerPort(GRIP_SERVER_TEST_PORT);\n+        this.serverFactory = new TestServerFactory();\n+        instance = new GripServer(serverFactory, () -> mockSettings, new Project());\n+        instance.start();\n+\n+        client = new DefaultHttpClient();\n+        client.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);\n+    }\n+\n+    /**\n+     * Test of GetHandler methods\n+     */\n+    @Test\n+    public void testGetHandlers() throws IOException {\n+        String path = \"/testGetHandlers\";\n+        GetHandler handler = params -> path;\n+        instance.addGetHandler(path, handler);\n+        instance.addDataSupplier(path, () -> path);\n+        assertEquals(\"The data supplied on \" + path + \" was not correct\", path, doGet(path));\n+    }\n+\n+    @Test\n+    public void testAddAndRemovePostHandler() throws IOException {\n+        final String path = \"/testAddPostHandler\";\n+        final byte[] testBytes = \"testAddPostHandler\".getBytes();\n+        final boolean[] didHandle = {false};\n+        PostHandler handler = bytes -> {\n+            didHandle[0] = true;\n+            return Arrays.equals(bytes, testBytes);\n+        };\n+\n+        instance.addPostHandler(path, handler);\n+        assertTrue(\"The server should have removed the handler\", instance.removePostHandler(handler));\n+        doPost(path, testBytes);\n+        assertFalse(\"Handler should not have run\", didHandle[0]);\n+    }\n+\n+    @Test\n+    public void testSuccessfulPostHandler() throws IOException {\n+        final String path = \"/testSuccessfulPostHandler\";\n+        final byte[] testBytes = \"testAddPostHandler\".getBytes();\n+        final boolean[] didHandle = {false};\n+        PostHandler handler = bytes -> {\n+            didHandle[0] = true;\n+            return Arrays.equals(bytes, testBytes);\n+        };\n+\n+        instance.addPostHandler(path, handler);\n+        HttpResponse response = doPost(path, testBytes);\n+        assertEquals(\"The server should return an OK status (200)\", 200, response.getStatusLine().getStatusCode());\n+        assertTrue(\"Handler should have run\", didHandle[0]);\n+        assertTrue(\"The server should have removed the handler\", instance.removePostHandler(handler));\n+    }\n+\n+    @Test\n+    public void testUnsuccessfulPostHandler() throws IOException {\n+        final String path = \"/testUnsuccessfulPostHandler\";\n+        final byte[] testBytes = new byte[1];\n+        final boolean[] didHandle = {false};\n+        PostHandler handler = bytes -> {\n+            didHandle[0] = true;\n+            throw new GripServerException(\"Expected\");\n+        };\n+\n+        instance.addPostHandler(path, handler);\n+        HttpResponse response = doPost(path, testBytes);\n+        assertEquals(\"Server should return an internal error (500)\", 500, response.getStatusLine().getStatusCode());\n+        assertTrue(\"Handler should have run\", didHandle[0]);\n+    }\n+\n+    @Test\n+    public void testEmptyPostData() throws IOException {\n+        final String path = \"/testEmptyPostData\";\n+        final byte[] testBytes = new byte[0];\n+        PostHandler handler = bytes -> {\n+            fail(\"Handler should not have run if there is no data\");\n+            return false; // won't be reached\n+        };\n+\n+        instance.addPostHandler(path, handler);\n+        HttpResponse response = doPost(path, testBytes);\n+        assertEquals(\"Server should return an internal error (500)\", 500, response.getStatusLine().getStatusCode());\n+    }\n+\n+    @Test\n+    public void testAddRemoveDataSupplier() {\n+        String name = \"testDataSuppliers\";\n+        Supplier<?> supplier = () -> name;\n+        instance.addDataSupplier(name, supplier);\n+        assertTrue(\"Server should have a data supplier for \" + name, instance.hasDataSupplier(name));\n+        instance.removeDataSupplier(name);\n+        assertFalse(\"Server should no longer have a data supplier for \" + name, instance.hasDataSupplier(name));\n+    }\n+\n+    @Test\n+    public void testNoSupplier() throws IOException {\n+        assertEquals(\"There shouldn't be any data on the server\", \"{}\", doGet(GripServer.DATA_PATH));\n+        assertEquals(\"There shouldn't be any data on the server\", \"{}\", doGet(GripServer.DATA_PATH + \"?no_data_here\"));\n+    }\n+\n+    @Test\n+    public void testWithSupplier() throws IOException {\n+        final Gson gson = new GsonBuilder().setPrettyPrinting().create();\n+        final String dataName = \"testWithSupplier\";\n+        final Map data = ImmutableMap.of(\"foo\", \"bar\");\n+        instance.addDataSupplier(dataName, () -> data);\n+        assertEquals(\"Generated json was malformed\", gson.toJson(ImmutableMap.of(dataName, data)), doGet(GripServer.DATA_PATH + \"?\" + dataName));\n+        assertEquals(\"Generated json was malformed\", gson.toJson(ImmutableMap.of(dataName, data)), doGet(GripServer.DATA_PATH));\n+        assertEquals(\"There shouldn't be any data on this path\", \"{}\", doGet(GripServer.DATA_PATH + \"?no_data_here\"));\n+    }\n+\n+    @Test\n+    public void testStartStop() throws GripException, IllegalStateException {","path":"core/src/test/java/edu/wpi/grip/core/http/GripServerTest.java","position":194,"original_position":194,"commit_id":"f85e51d8ec2bdaea7e6bc950e84c24e069643aa1","original_commit_id":"f85e51d8ec2bdaea7e6bc950e84c24e069643aa1","user":{"login":"WPIRoboticsProjects-Bot","id":14011429,"avatar_url":"https://avatars.githubusercontent.com/u/14011429?v=3","gravatar_id":"","url":"https://api.github.com/users/WPIRoboticsProjects-Bot","html_url":"https://github.com/WPIRoboticsProjects-Bot","followers_url":"https://api.github.com/users/WPIRoboticsProjects-Bot/followers","following_url":"https://api.github.com/users/WPIRoboticsProjects-Bot/following{/other_user}","gists_url":"https://api.github.com/users/WPIRoboticsProjects-Bot/gists{/gist_id}","starred_url":"https://api.github.com/users/WPIRoboticsProjects-Bot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/WPIRoboticsProjects-Bot/subscriptions","organizations_url":"https://api.github.com/users/WPIRoboticsProjects-Bot/orgs","repos_url":"https://api.github.com/users/WPIRoboticsProjects-Bot/repos","events_url":"https://api.github.com/users/WPIRoboticsProjects-Bot/events{/privacy}","received_events_url":"https://api.github.com/users/WPIRoboticsProjects-Bot/received_events","type":"User","site_admin":false},"body":"![Codacy](https://www.codacy.com/assets/images/favicon.png) Issue found: [JUnit tests should include assert() or fail()](https://www.codacy.com/app/WPIRoboticsProjects/GRIP/file/2444711314/issues/source?bid=2943107&fileBranchId=3205212#l194)","created_at":"2016-04-21T17:23:58Z","updated_at":"2016-04-21T17:23:58Z","html_url":"https://github.com/WPIRoboticsProjects/GRIP/pull/551#discussion_r60621291","pull_request_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls/551","_links":{"self":{"href":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls/comments/60621291"},"html":{"href":"https://github.com/WPIRoboticsProjects/GRIP/pull/551#discussion_r60621291"},"pull_request":{"href":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls/551"}}},"pull_request":{"url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls/551","id":63943653,"html_url":"https://github.com/WPIRoboticsProjects/GRIP/pull/551","diff_url":"https://github.com/WPIRoboticsProjects/GRIP/pull/551.diff","patch_url":"https://github.com/WPIRoboticsProjects/GRIP/pull/551.patch","issue_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/issues/551","number":551,"state":"open","locked":false,"title":"Added HTTP publishing and image source operations","user":{"login":"SamCarlberg","id":6320992,"avatar_url":"https://avatars.githubusercontent.com/u/6320992?v=3","gravatar_id":"","url":"https://api.github.com/users/SamCarlberg","html_url":"https://github.com/SamCarlberg","followers_url":"https://api.github.com/users/SamCarlberg/followers","following_url":"https://api.github.com/users/SamCarlberg/following{/other_user}","gists_url":"https://api.github.com/users/SamCarlberg/gists{/gist_id}","starred_url":"https://api.github.com/users/SamCarlberg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SamCarlberg/subscriptions","organizations_url":"https://api.github.com/users/SamCarlberg/orgs","repos_url":"https://api.github.com/users/SamCarlberg/repos","events_url":"https://api.github.com/users/SamCarlberg/events{/privacy}","received_events_url":"https://api.github.com/users/SamCarlberg/received_events","type":"User","site_admin":false},"body":"## How to use the publishing operations\r\n Use them in the UI just like a NetworkTable publish operation\r\n\r\n#### Getting the data\r\nAll data published will be at `http://localhost:8080/GRIP/data` (or whatever port you set in the settings) in json format. To get specific data, put their names as parameters in the URL -- e.g. if you want `myContoursReport` and `myBlobsReport`, go to `http://localhost:8080/GRIP/data?myContoursReport&myBlobsReport`\r\n\r\n### HTTP image source\r\n\r\n#### GRIP-side\r\nJust use it like a normal image source. It may be a good idea to have the URL be specifiable rather than the static `/GRIP/upload/image` path.\r\n\r\n#### Supplier-side\r\nPerform an HTTP `POST` operation to `http://localhost:8080/GRIP/upload/image` with the image bytes as the data. The server will send those bytes to handlers (currently just one, the `HTTPSource` instance on `/GRIP/upload/image`) that will encode those bytes into `Mats`.\r\n\r\n## Known issues\r\n~~Every other GET/POST request to the server will time out. You can see this by just pointing to http://localhost:8080/GRIP/data in your browser and refreshing the page~~. Fixed in 3162f50","created_at":"2016-03-23T20:00:38Z","updated_at":"2016-04-21T17:23:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"223d57e0b0fa3ef2cbfa0d1ad0eafcb0e1597303","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls/551/commits","review_comments_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls/551/comments","review_comment_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls/comments{/number}","comments_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/issues/551/comments","statuses_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/statuses/f85e51d8ec2bdaea7e6bc950e84c24e069643aa1","head":{"label":"SamCarlberg:feat/http","ref":"feat/http","sha":"f85e51d8ec2bdaea7e6bc950e84c24e069643aa1","user":{"login":"SamCarlberg","id":6320992,"avatar_url":"https://avatars.githubusercontent.com/u/6320992?v=3","gravatar_id":"","url":"https://api.github.com/users/SamCarlberg","html_url":"https://github.com/SamCarlberg","followers_url":"https://api.github.com/users/SamCarlberg/followers","following_url":"https://api.github.com/users/SamCarlberg/following{/other_user}","gists_url":"https://api.github.com/users/SamCarlberg/gists{/gist_id}","starred_url":"https://api.github.com/users/SamCarlberg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SamCarlberg/subscriptions","organizations_url":"https://api.github.com/users/SamCarlberg/orgs","repos_url":"https://api.github.com/users/SamCarlberg/repos","events_url":"https://api.github.com/users/SamCarlberg/events{/privacy}","received_events_url":"https://api.github.com/users/SamCarlberg/received_events","type":"User","site_admin":false},"repo":{"id":54142171,"name":"GRIP","full_name":"SamCarlberg/GRIP","owner":{"login":"SamCarlberg","id":6320992,"avatar_url":"https://avatars.githubusercontent.com/u/6320992?v=3","gravatar_id":"","url":"https://api.github.com/users/SamCarlberg","html_url":"https://github.com/SamCarlberg","followers_url":"https://api.github.com/users/SamCarlberg/followers","following_url":"https://api.github.com/users/SamCarlberg/following{/other_user}","gists_url":"https://api.github.com/users/SamCarlberg/gists{/gist_id}","starred_url":"https://api.github.com/users/SamCarlberg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SamCarlberg/subscriptions","organizations_url":"https://api.github.com/users/SamCarlberg/orgs","repos_url":"https://api.github.com/users/SamCarlberg/repos","events_url":"https://api.github.com/users/SamCarlberg/events{/privacy}","received_events_url":"https://api.github.com/users/SamCarlberg/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/SamCarlberg/GRIP","description":"Program for rapidly developing computer vision applications","fork":true,"url":"https://api.github.com/repos/SamCarlberg/GRIP","forks_url":"https://api.github.com/repos/SamCarlberg/GRIP/forks","keys_url":"https://api.github.com/repos/SamCarlberg/GRIP/keys{/key_id}","collaborators_url":"https://api.github.com/repos/SamCarlberg/GRIP/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/SamCarlberg/GRIP/teams","hooks_url":"https://api.github.com/repos/SamCarlberg/GRIP/hooks","issue_events_url":"https://api.github.com/repos/SamCarlberg/GRIP/issues/events{/number}","events_url":"https://api.github.com/repos/SamCarlberg/GRIP/events","assignees_url":"https://api.github.com/repos/SamCarlberg/GRIP/assignees{/user}","branches_url":"https://api.github.com/repos/SamCarlberg/GRIP/branches{/branch}","tags_url":"https://api.github.com/repos/SamCarlberg/GRIP/tags","blobs_url":"https://api.github.com/repos/SamCarlberg/GRIP/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/SamCarlberg/GRIP/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/SamCarlberg/GRIP/git/refs{/sha}","trees_url":"https://api.github.com/repos/SamCarlberg/GRIP/git/trees{/sha}","statuses_url":"https://api.github.com/repos/SamCarlberg/GRIP/statuses/{sha}","languages_url":"https://api.github.com/repos/SamCarlberg/GRIP/languages","stargazers_url":"https://api.github.com/repos/SamCarlberg/GRIP/stargazers","contributors_url":"https://api.github.com/repos/SamCarlberg/GRIP/contributors","subscribers_url":"https://api.github.com/repos/SamCarlberg/GRIP/subscribers","subscription_url":"https://api.github.com/repos/SamCarlberg/GRIP/subscription","commits_url":"https://api.github.com/repos/SamCarlberg/GRIP/commits{/sha}","git_commits_url":"https://api.github.com/repos/SamCarlberg/GRIP/git/commits{/sha}","comments_url":"https://api.github.com/repos/SamCarlberg/GRIP/comments{/number}","issue_comment_url":"https://api.github.com/repos/SamCarlberg/GRIP/issues/comments{/number}","contents_url":"https://api.github.com/repos/SamCarlberg/GRIP/contents/{+path}","compare_url":"https://api.github.com/repos/SamCarlberg/GRIP/compare/{base}...{head}","merges_url":"https://api.github.com/repos/SamCarlberg/GRIP/merges","archive_url":"https://api.github.com/repos/SamCarlberg/GRIP/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/SamCarlberg/GRIP/downloads","issues_url":"https://api.github.com/repos/SamCarlberg/GRIP/issues{/number}","pulls_url":"https://api.github.com/repos/SamCarlberg/GRIP/pulls{/number}","milestones_url":"https://api.github.com/repos/SamCarlberg/GRIP/milestones{/number}","notifications_url":"https://api.github.com/repos/SamCarlberg/GRIP/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/SamCarlberg/GRIP/labels{/name}","releases_url":"https://api.github.com/repos/SamCarlberg/GRIP/releases{/id}","deployments_url":"https://api.github.com/repos/SamCarlberg/GRIP/deployments","created_at":"2016-03-17T18:30:55Z","updated_at":"2016-03-17T18:30:56Z","pushed_at":"2016-04-21T17:21:42Z","git_url":"git://github.com/SamCarlberg/GRIP.git","ssh_url":"git@github.com:SamCarlberg/GRIP.git","clone_url":"https://github.com/SamCarlberg/GRIP.git","svn_url":"https://github.com/SamCarlberg/GRIP","homepage":"http://wpiroboticsprojects.github.io/GRIP","size":40559,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"WPIRoboticsProjects:master","ref":"master","sha":"18a7a4ad86227a3e4996ab3393ef96247878258d","user":{"login":"WPIRoboticsProjects","id":14004340,"avatar_url":"https://avatars.githubusercontent.com/u/14004340?v=3","gravatar_id":"","url":"https://api.github.com/users/WPIRoboticsProjects","html_url":"https://github.com/WPIRoboticsProjects","followers_url":"https://api.github.com/users/WPIRoboticsProjects/followers","following_url":"https://api.github.com/users/WPIRoboticsProjects/following{/other_user}","gists_url":"https://api.github.com/users/WPIRoboticsProjects/gists{/gist_id}","starred_url":"https://api.github.com/users/WPIRoboticsProjects/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/WPIRoboticsProjects/subscriptions","organizations_url":"https://api.github.com/users/WPIRoboticsProjects/orgs","repos_url":"https://api.github.com/users/WPIRoboticsProjects/repos","events_url":"https://api.github.com/users/WPIRoboticsProjects/events{/privacy}","received_events_url":"https://api.github.com/users/WPIRoboticsProjects/received_events","type":"Organization","site_admin":false},"repo":{"id":41505521,"name":"GRIP","full_name":"WPIRoboticsProjects/GRIP","owner":{"login":"WPIRoboticsProjects","id":14004340,"avatar_url":"https://avatars.githubusercontent.com/u/14004340?v=3","gravatar_id":"","url":"https://api.github.com/users/WPIRoboticsProjects","html_url":"https://github.com/WPIRoboticsProjects","followers_url":"https://api.github.com/users/WPIRoboticsProjects/followers","following_url":"https://api.github.com/users/WPIRoboticsProjects/following{/other_user}","gists_url":"https://api.github.com/users/WPIRoboticsProjects/gists{/gist_id}","starred_url":"https://api.github.com/users/WPIRoboticsProjects/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/WPIRoboticsProjects/subscriptions","organizations_url":"https://api.github.com/users/WPIRoboticsProjects/orgs","repos_url":"https://api.github.com/users/WPIRoboticsProjects/repos","events_url":"https://api.github.com/users/WPIRoboticsProjects/events{/privacy}","received_events_url":"https://api.github.com/users/WPIRoboticsProjects/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/WPIRoboticsProjects/GRIP","description":"Program for rapidly developing computer vision applications","fork":false,"url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP","forks_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/forks","keys_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/keys{/key_id}","collaborators_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/teams","hooks_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/hooks","issue_events_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/issues/events{/number}","events_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/events","assignees_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/assignees{/user}","branches_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/branches{/branch}","tags_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/tags","blobs_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/git/refs{/sha}","trees_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/git/trees{/sha}","statuses_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/statuses/{sha}","languages_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/languages","stargazers_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/stargazers","contributors_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/contributors","subscribers_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/subscribers","subscription_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/subscription","commits_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/commits{/sha}","git_commits_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/git/commits{/sha}","comments_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/comments{/number}","issue_comment_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/issues/comments{/number}","contents_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/contents/{+path}","compare_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/compare/{base}...{head}","merges_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/merges","archive_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/downloads","issues_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/issues{/number}","pulls_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls{/number}","milestones_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/milestones{/number}","notifications_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/labels{/name}","releases_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/releases{/id}","deployments_url":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/deployments","created_at":"2015-08-27T18:59:07Z","updated_at":"2016-04-15T15:15:17Z","pushed_at":"2016-04-21T17:21:43Z","git_url":"git://github.com/WPIRoboticsProjects/GRIP.git","ssh_url":"git@github.com:WPIRoboticsProjects/GRIP.git","clone_url":"https://github.com/WPIRoboticsProjects/GRIP.git","svn_url":"https://github.com/WPIRoboticsProjects/GRIP","homepage":"http://wpiroboticsprojects.github.io/GRIP","size":46488,"stargazers_count":102,"watchers_count":102,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":41,"mirror_url":null,"open_issues_count":97,"forks":41,"open_issues":97,"watchers":102,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls/551"},"html":{"href":"https://github.com/WPIRoboticsProjects/GRIP/pull/551"},"issue":{"href":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/issues/551"},"comments":{"href":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/issues/551/comments"},"review_comments":{"href":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls/551/comments"},"review_comment":{"href":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/pulls/551/commits"},"statuses":{"href":"https://api.github.com/repos/WPIRoboticsProjects/GRIP/statuses/f85e51d8ec2bdaea7e6bc950e84c24e069643aa1"}}}}