Browse Source

implement status API

pull/3/head
Aaron Parecki 8 years ago
parent
commit
a711422b7e
7 changed files with 192 additions and 40 deletions
  1. +66
    -10
      README.md
  2. +5
    -5
      composer.lock
  3. +61
    -2
      controllers/API.php
  4. +6
    -6
      lib/Telegraph/HTTP.php
  5. +20
    -7
      lib/Telegraph/Webmention.php
  6. +24
    -3
      tests/APITest.php
  7. +10
    -7
      tests/ProcessTest.php

+ 66
- 10
README.md View File

@ -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

+ 5
- 5
composer.lock View File

@ -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",

+ 61
- 2
controllers/API.php View File

@ -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);
}
}

+ 6
- 6
lib/Telegraph/HTTP.php View File

@ -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)),
);
}

+ 20
- 7
lib/Telegraph/Webmention.php View File

@ -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']);
}

+ 24
- 3
tests/APITest.php View File

@ -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);
}
}

+ 10
- 7
tests/ProcessTest.php View File

@ -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']));

Loading…
Cancel
Save