From a711422b7e34d451e75c217b906eff36c6c93318 Mon Sep 17 00:00:00 2001 From: Aaron Parecki Date: Tue, 22 Dec 2015 16:02:08 -0800 Subject: [PATCH] implement status API --- README.md | 76 +++++++++++++++++++++++++++++++----- composer.lock | 10 ++--- controllers/API.php | 63 +++++++++++++++++++++++++++++- lib/Telegraph/HTTP.php | 12 +++--- lib/Telegraph/Webmention.php | 27 +++++++++---- tests/APITest.php | 27 +++++++++++-- tests/ProcessTest.php | 17 ++++---- 7 files changed, 192 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 630a9cf..acc9ac4 100644 --- a/README.md +++ b/README.md @@ -44,28 +44,84 @@ Content-type: application/json Location: https://telegraph.p3k.io/webmention/xxxxxxxx { - "result": "queued", - "status": "https://telegraph.p3k.io/webmention/xxxxxxxx" + "status": "queued", + "location": "https://telegraph.p3k.io/webmention/xxxxxxxx" } ``` -### Callback Events -After Telegraph processes your request, you will receive a post to the callback URL. The initial callback you receive will be one of the following status codes: +### Status API + +You can poll the status URL returned after queuing a webmention for more information on the progress of sending the webmention. The response will look like the following: + +``` +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "status": "queued", + "summary": "The webmention is still in the processing queue.", + "location": "https://telegraph.p3k.io/webmention/xxxxxxxx" +} +``` + +``` +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "status": "no_link_found", + "summary": "No link was found from source to target" +} +``` + +``` +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "status": "success", + "type": "webmention", + "endpoint": + "summary": "The webmention request was accepted.", + "location": "https://telegraph.p3k.io/webmention/xxxxxxxx" +} +``` + +The possible fields that are returned are as follows: + +* `status` - One of the status codes listed below +* `type` - optional - "webmention" or "pingback", depending on what was discovered at the target +* `endpoint` - optional - The webmention or pingback endpoint that was discovered +* `http_code` - optional - The HTTP code that the webmention or pingback endpoint returned +* `summary` - optional - A human-readable summary of the status +* `location` - optional - If present, you can continue checking this URL for status updates. If not present, no further information will be available about this request. + +Other possible status codes are listed below. -* `not_supported` - no webmention or pingback endpoint found -* `webmention_accepted` - the webmention request was accepted -* `webmention_error` - the webmention endpoint returned an error code -* `pingback_accepted` - pingback was accepted (pingback does not differentiate between when a request is queued or processed immediately) -* `pingback_error` - the pingback endpoint returned an error code +* `accepted` - the webmention or pingback request was accepted (pingback does not differentiate between when a request is queued or processed immediately) +* `success` - the webmention status endpoint indicated the webmention was successful after processing it +* `not_supported` - no webmention or pingback endpoint was found at the target +* `no_link_found` - no link was found from source to target + +Other status codes may be returned depending on the receiver's status endpoint. You should only assume a webmention was successfully sent if the status is `success` or `accepted`. If the response does not contain a `location` parameter you should not continue polling the endpoint. + + +### Callback Events +After Telegraph processes your request, you will receive a post to the callback URL. The initial callback you receive will be one of the status codes returned by the status API. Typically, webmention endpoints defer processing until later, so normally the first callback received will indicate that the webmention was queued. This callback will normally be sent relatively quickly after you make the initial request, typically within a few seconds. If the webmention endpoint provides status updates, either through a status URL or web hook, then Telegraph will deliver follow-up notifications when it gets updated information. A callback from Telegraph will include the following post body parameters: + * `source` - the URL of your post * `target` - the URL you linked to -* `status` - one of the status codes above, e.g. `webmention_queued` +* `type` - "pingback" or "webmention" depending on what was discovered at the target +* `status` - one of the status codes above, e.g. `accepted` +* `location` - if further updates will be available, the status URL where you can check again in the future + + ## Credits diff --git a/composer.lock b/composer.lock index 9c1e000..e84bf6f 100644 --- a/composer.lock +++ b/composer.lock @@ -177,16 +177,16 @@ }, { "name": "indieweb/mention-client", - "version": "1.1.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/indieweb/mention-client-php.git", - "reference": "0bc331432e0490cc739b8a99d808889555c8d587" + "reference": "28115f604eb0c0d88a4b46a11771823af27e9e58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/indieweb/mention-client-php/zipball/0bc331432e0490cc739b8a99d808889555c8d587", - "reference": "0bc331432e0490cc739b8a99d808889555c8d587", + "url": "https://api.github.com/repos/indieweb/mention-client-php/zipball/28115f604eb0c0d88a4b46a11771823af27e9e58", + "reference": "28115f604eb0c0d88a4b46a11771823af27e9e58", "shasum": "" }, "require": { @@ -215,7 +215,7 @@ ], "description": "Client library for sending webmention and pingback notifications", "homepage": "https://github.com/indieweb/mention-client-php", - "time": "2015-12-22 02:48:43" + "time": "2015-12-22 23:40:11" }, { "name": "j4mie/idiorm", diff --git a/controllers/API.php b/controllers/API.php index a9489b7..1b1ae10 100644 --- a/controllers/API.php +++ b/controllers/API.php @@ -130,11 +130,70 @@ class API { $statusURL = Config::$base . 'webmention/' . $w->token; return $this->respond($response, 201, [ - 'result' => 'queued', - 'status' => $statusURL + 'status' => 'queued', + 'location' => $statusURL ], [ 'Location' => $statusURL ]); } + public function webmention_status(Request $request, Response $response, $args) { + + $webmention = ORM::for_table('webmentions')->where('token', $args['code'])->find_one(); + + if(!$webmention) { + return $this->respond($response, 404, [ + 'status' => 'not_found', + ]); + } + + $status = ORM::for_table('webmention_status')->where('webmention_id', $webmention->id)->order_by_desc('created_at')->find_one(); + + $statusURL = Config::$base . 'webmention/' . $webmention->token; + + if(!$status) { + $code = 'queued'; + } else { + $code = $status->status; + } + + $data = [ + 'status' => $code, + ]; + + if($webmention->webmention_endpoint) { + $data['type'] = 'webmention'; + $data['endpoint'] = $webmention->webmention_endpoint; + } + if($webmention->pingback_endpoint) { + $data['type'] = 'pingback'; + $data['endpoint'] = $webmention->pingback_endpoint; + } + + switch($code) { + case 'queued': + $summary = 'The webmention is still in the processing queue'; + break; + case 'not_supported': + $summary = 'No webmention or pingback endpoint were found at the target'; + break; + case 'accepted': + $summary = 'The '.$data['type'].' request was accepted'; + break; + default: + $summary = false; + } + + if($status && $status->http_code) + $data['http_code'] = (int)$status->http_code; + + if($summary) + $data['summary'] = $summary; + + if($webmention->complete == 0) + $data['location'] = $statusURL; + + return $this->respond($response, 200, $data); + } + } diff --git a/lib/Telegraph/HTTP.php b/lib/Telegraph/HTTP.php index 64e9117..4fe0809 100644 --- a/lib/Telegraph/HTTP.php +++ b/lib/Telegraph/HTTP.php @@ -10,8 +10,8 @@ class HTTP { $response = curl_exec($ch); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); return array( - 'status' => curl_getinfo($ch, CURLINFO_HTTP_CODE), - 'headers' => self::_parse_headers(trim(substr($response, 0, $header_size))), + 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), + 'headers' => self::parse_headers(trim(substr($response, 0, $header_size))), 'body' => substr($response, $header_size) ); } @@ -28,8 +28,8 @@ class HTTP { self::_debug($response); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); return array( - 'status' => curl_getinfo($ch, CURLINFO_HTTP_CODE), - 'headers' => self::_parse_headers(trim(substr($response, 0, $header_size))), + 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), + 'headers' => self::parse_headers(trim(substr($response, 0, $header_size))), 'body' => substr($response, $header_size) ); } @@ -42,8 +42,8 @@ class HTTP { if (self::$_proxy) curl_setopt($ch, CURLOPT_PROXY, self::$_proxy); $response = curl_exec($ch); return array( - 'status' => curl_getinfo($ch, CURLINFO_HTTP_CODE), - 'headers' => self::_parse_headers(trim($response)), + 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), + 'headers' => self::parse_headers(trim($response)), ); } diff --git a/lib/Telegraph/Webmention.php b/lib/Telegraph/Webmention.php index ed0a432..0ec2dbc 100644 --- a/lib/Telegraph/Webmention.php +++ b/lib/Telegraph/Webmention.php @@ -20,11 +20,19 @@ class Webmention { // Post to the callback URL if one is set if($webmention->callback) { - return self::$http->post($webmention->callback, [ + $payload = [ 'source' => $webmention->source, 'target' => $webmention->target, 'status' => $code - ]); + ]; + if($webmention->webmention_endpoint) { + $payload['type'] = 'webmention'; + } + if($webmention->pingback_endpoint) { + $payload['type'] = 'pingback'; + } + + return self::$http->post($webmention->callback, $payload); } } @@ -39,7 +47,7 @@ class Webmention { $client = new MentionClient(); if(!$http) - $http = new Telegraph\HTTP(); + $http = new HTTP(); self::$http = $http; @@ -59,7 +67,7 @@ class Webmention { $webmention->save(); $success = $client->sendPingbackToEndpoint($pingbackEndpoint, $webmention->source, $webmention->target); - return self::updateStatus($webmention, null, ($success ? 'pingback_accepted' : 'pingback_error')); + return self::updateStatus($webmention, null, ($success ? 'accepted' : 'error')); } // There is a webmention endpoint, send the webmention now @@ -70,18 +78,23 @@ class Webmention { $response = $client->sendWebmentionToEndpoint($endpoint, $webmention->source, $webmention->target); if(in_array($response['code'], [200,201,202])) { - $status = 'webmention_accepted'; + $status = 'accepted'; + + $webmention->complete = $response['code'] == 200 ? 1 : 0; // Check if the endpoint returned a status URL if(array_key_exists('Location', $response['headers'])) { $webmention->webmention_status_url = \Mf2\resolveUrl($endpoint, $response['headers']['Location']); - $webmention->save(); + // TODO: queue a job to poll the endpoint for updates and deliver to the callback URL } } else { - $status = 'webmention_error'; + $webmention->complete = 1; + $status = 'error'; } + $webmention->save(); + return self::updateStatus($webmention, $response['code'], $status, $response['body']); } diff --git a/tests/APITest.php b/tests/APITest.php index 3d5d048..cf53e73 100644 --- a/tests/APITest.php +++ b/tests/APITest.php @@ -22,6 +22,12 @@ class APITest extends PHPUnit_Framework_TestCase { return $this->client->webmention($request, $response); } + private function status($code) { + $request = new Request(); + $response = new Response(); + return $this->client->webmention_status($request, $response, ['code'=>$code]); + } + private function _createExampleAccount() { $user = ORM::for_table('users')->create(); $user->url = 'http://example.com'; @@ -127,16 +133,31 @@ class APITest extends PHPUnit_Framework_TestCase { $this->assertEquals(201, $response->getStatusCode()); $data = json_decode($response->getContent()); $this->assertEquals(false, property_exists($data, 'error')); - $this->assertEquals('queued', $data->result); - $this->assertEquals(true, property_exists($data, 'status')); + $this->assertEquals('queued', $data->status); + $this->assertEquals(true, property_exists($data, 'location')); - preg_match('/\/webmention\/(.+)/', $data->status, $match); + preg_match('/\/webmention\/(.+)/', $data->location, $match); $this->assertNotNull($match); # Verify it queued the mention in the database $d = ORM::for_table('webmentions')->where(['source' => 'http://source.example.com/basictest', 'target' => 'http://target.example.com'])->find_one(); $this->assertNotNull($d); $this->assertEquals($match[1], $d->token); + + # Check the status endpoint to make sure it says it's still queued + $response = $this->status($d->token); + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($response->getContent()); + $this->assertEquals('queued', $data->status); + } + + public function testStatusNotFound() { + $this->_createExampleAccount(); + + $response = $this->status('foo'); + $this->assertEquals(404, $response->getStatusCode()); + $data = json_decode($response->getContent()); + $this->assertEquals('not_found', $data->status); } } diff --git a/tests/ProcessTest.php b/tests/ProcessTest.php index 3f2dce8..3aecd2b 100644 --- a/tests/ProcessTest.php +++ b/tests/ProcessTest.php @@ -73,7 +73,7 @@ class ProcessTest extends PHPUnit_Framework_TestCase { 'target' => 'http://target.example.com/pingback-success' ]); $status = $this->webmentionStatus($webmention->id); - $this->assertEquals($status->status, 'pingback_accepted'); + $this->assertEquals('accepted', $status->status); $webmention = ORM::for_table('webmentions')->where('id',$webmention->id)->find_one(); $this->assertEquals('http://pingback.example.com/success', $webmention->pingback_endpoint); } @@ -86,7 +86,7 @@ class ProcessTest extends PHPUnit_Framework_TestCase { 'target' => 'http://target.example.com/pingback-failed' ]); $status = $this->webmentionStatus($webmention->id); - $this->assertEquals($status->status, 'pingback_error'); + $this->assertEquals('error', $status->status); } public function testWebmentionTakesPriorityOverPingback() { @@ -97,7 +97,10 @@ class ProcessTest extends PHPUnit_Framework_TestCase { 'target' => 'http://target.example.com/webmention-success' ]); $status = $this->webmentionStatus($webmention->id); - $this->assertEquals($status->status, 'webmention_accepted'); + $webmention = ORM::for_table('webmentions')->where('id',$webmention->id)->find_one(); + $this->assertNotNull($webmention->webmention_endpoint); + $this->assertNull($webmention->pingback_endpoint); + $this->assertEquals('accepted', $status->status); } public function testWebmentionSucceeds() { @@ -108,7 +111,7 @@ class ProcessTest extends PHPUnit_Framework_TestCase { 'target' => 'http://target.example.com/webmention-success' ]); $status = $this->webmentionStatus($webmention->id); - $this->assertEquals($status->status, 'webmention_accepted'); + $this->assertEquals('accepted', $status->status); $webmention = ORM::for_table('webmentions')->where('id',$webmention->id)->find_one(); $this->assertEquals('http://webmention.example.com/success', $webmention->webmention_endpoint); } @@ -121,7 +124,7 @@ class ProcessTest extends PHPUnit_Framework_TestCase { 'target' => 'http://target.example.com/webmention-status-url' ]); $status = $this->webmentionStatus($webmention->id); - $this->assertEquals($status->status, 'webmention_accepted'); + $this->assertEquals('accepted', $status->status); $webmention = ORM::for_table('webmentions')->where('id',$webmention->id)->find_one(); $this->assertEquals('http://webmention.example.com/success-with-status', $webmention->webmention_endpoint); // Make sure the status URL returned is an absolute URL @@ -136,7 +139,7 @@ class ProcessTest extends PHPUnit_Framework_TestCase { 'target' => 'http://target.example.com/webmention-failed' ]); $status = $this->webmentionStatus($webmention->id); - $this->assertEquals($status->status, 'webmention_error'); + $this->assertEquals('error', $status->status); $webmention = ORM::for_table('webmentions')->where('id',$webmention->id)->find_one(); $this->assertEquals('http://webmention.example.com/error', $webmention->webmention_endpoint); } @@ -150,7 +153,7 @@ class ProcessTest extends PHPUnit_Framework_TestCase { 'callback' => 'http://source.example.com/callback' ]); $status = $this->webmentionStatus($webmention->id); - $this->assertEquals($status->status, 'webmention_accepted'); + $this->assertEquals('accepted', $status->status); $webmention = ORM::for_table('webmentions')->where('id',$webmention->id)->find_one(); $this->assertEquals('http://webmention.example.com/success', $webmention->webmention_endpoint); $this->assertEquals('Callback was successful', trim($callback['body']));