Is there stomp over websocket in PHP? - php

I use Node.js to run STOMP over WebSocket because it supports custom headers. When HTTP handshake, the server needs a cookie to check auth, not the token in the address.
As PHPer, I want to use this in PHP, but search google for a long time and found no way. can anyone help?

STOMP is not limited to Node.js, or Java, etc. There are
a STOMP PECL extension for PHP,
PHP STOMP libraries, e.g Stomp-PHP,
and general messaging libraries that support STOMP,
e.g. Enqueue's STOMP transport layer, or Laravel-Queue
Regarding your specific scenario, it's hard to come up with a straight-up answer without knowing more details but here is an example from the stomp-php library samples hat sets a custom header:
use Stomp\Client;
use Stomp\SimpleStomp;
use Stomp\Transport\Map;
// make a connection
$client = new Client('tcp://localhost:61613');
$stomp = new SimpleStomp($client);
// send a message to the queue
$body = array('city' => 'Belgrade', 'name' => 'Dejan');
$header = array();
$header['transformation'] = 'jms-map-json';
$mapMessage = new Map($body, $header);
$client->send('/queue/test', $mapMessage);
echo 'Sending array: ';
print_r($body);
$stomp->subscribe('/queue/test', 'transform-test', 'client', null, ['transformation' => 'jms-map-json']);
/** #var Map $msg */
$msg = $stomp->read();
// extract
if ($msg != null) {
echo 'Received array: ';
print_r($msg->map);
// mark the message as received in the queue
$stomp->ack($msg);
} else {
echo "Failed to receive a message\n";
}

you need a websocket stream wrapper if you want to connect using a protocol

You can use php-stomp-frame
https://github.com/jeeinn/php-stomp-frame
composer require jeeinn/php-stomp-frame
public function getStompMessage(): array
{
$stompFrame = new Frame();
$connectFrame = $stompFrame->setLogin($this->ss['login'], $this->ss['password'])
->setHeartBeat(0, 10000)->getConnect();
$subscribeFrame = $stompFrame->getSubscribe($this->ss['queue']);
$client = new Client($this->ss['host']);
$client->text($connectFrame);
$client->text($subscribeFrame);
$response = [];
$i = 0;
while ($i < 3) {
sleep(5);
try {
$message = $client->receive();
if (empty($message) || in_array($message, self::BYTE)) {
$i++;
continue;
}
$parsed = $stompFrame->parser($message);
if ($parsed['command'] == $this::STOMP_COMMAND_ERROR) {
$this->logger($this::INFO, 'STOMP return with ERROR command:', $parsed['body']);
$client->close();
break;
}
if (($parsed['command'] == $this::STOMP_COMMAND_MESSAGE) || ($parsed['body'])) {
return json_decode($parsed['body'], true);
}
} catch (Exception $e) {
$this->logger($this::ERROR, 'STOMP error', (array)$e->getMessage());
}
$i++;
}
return $response;
}

Related

PHP - TCP/IP fsockopen

I was wondering if anyone has had any experience with this before. I'm trying to write a simple script that will continously read data from the TCP/IP stream but for some reason or another the script reads in a bunch of data, writes it out and then just stops.
$fp = fsockopen("xxxx", 3000, $errno, $errstr, 5);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
while (!feof($fp)) {
echo fgets($fp, 128)."\n";
fflush($fp);
}
fclose($fp);
}
I'd like it to have a constant flow to it, rather then echo out a bunch of data then wait 30 seconds and output a bunch more data. Anyone have any ideas?
---- EDIT ----
ZMQ Code
include 'zmsg.php';
$context = new ZMQContext();
$client = new ZMQSocket($context, ZMQ::SOCKET_DEALER);
// Generate printable identity for the client
$identity = sprintf ("%04X", rand(0, 0x10000));
$client->setSockOpt(ZMQ::SOCKOPT_IDENTITY, $identity);
$client->connect("tcp://xxxx:3000");
$read = $write = array();
$poll = new ZMQPoll();
$poll->add($client, ZMQ::POLL_IN);
$request_nbr = 0;
while (true) {
// Tick once per second, pulling in arriving messages
for ($centitick = 0; $centitick < 100; $centitick++) {
$events = $poll->poll($read, $write, 1000);
$zmsg = new Zmsg($client);
if ($events) {
$zmsg->recv();
echo $zmsg->body()."\n";
//printf ("%s: %s%s", $identity, $zmsg->body(), PHP_EOL);
}
}
$zmsg = new Zmsg($client);
//$zmsg->body_fmt("request #%d", ++$request_nbr)->send();
}
Here is how you connect to a server (as a client) if your goal is ONLY to PULL data (read).
<?php
$context = new ZMQContext();
$sock = new ZMQSocket($context, ZMQ::SOCKET_PULL);
$sock->connect("tcp://ADDRESS:3000");
while (true)
{
$request = $sock->recv(); # recv is blocking by default, no need to put timers.
printf ("Received: %s;%s", $request, PHP_EOL);
}
?>
if you want to reply, you'll need to use a pair socket (ZMQ::SOCKET_PAIR), then you can use:
$sock->send("data to send");
Also, if instead of you connecting to clients, clients connects to you, use the bind method instead of connect.
EDIT: use the PUSH socket type on the other side if you use the pull here, else, use the pair socket on both sides.

How to perform multiple Guzzle requests at the same time?

I can perform single requests using Guzzle and I'm very pleased with Guzzle's performance so far however, I read in the Guzzle API something about MultiCurl and Batching.
Could someone explain to me how to make multiple requests at the same time? Async if possible. I don't know if that is what they mean with MultiCurl. Sync would also be not a problem. I just want to do multiple requests at the same time or very close (short space of time).
From the docs:
http://guzzle3.readthedocs.org/http-client/client.html#sending-requests-in-parallel
For an easy to use solution that returns a hash of request objects mapping to a response or error, see http://guzzle3.readthedocs.org/batching/batching.html#batching
Short example:
<?php
$client->send(array(
$client->get('http://www.example.com/foo'),
$client->get('http://www.example.com/baz'),
$client->get('http://www.example.com/bar')
));
An update related to the new GuzzleHttp guzzlehttp/guzzle
Concurrent/parallel calls are now run through a few different methods including Promises.. Concurrent Requests
The old way of passing a array of RequestInterfaces will not work anymore.
See example here
$newClient = new \GuzzleHttp\Client(['base_uri' => $base]);
foreach($documents->documents as $doc){
$params = [
'language' =>'eng',
'text' => $doc->summary,
'apikey' => $key
];
$requestArr[$doc->reference] = $newClient->getAsync( '/1/api/sync/analyze/v1?' . http_build_query( $params) );
}
$time_start = microtime(true);
$responses = \GuzzleHttp\Promise\unwrap($requestArr); //$newClient->send( $requestArr );
$time_end = microtime(true);
$this->get('logger')->error(' NewsPerf Dev: took ' . ($time_end - $time_start) );
Update:
As suggested in comments and asked by #sankalp-tambe, you can also use a different approach to avoid that a set of concurrent request with a failure will not return all the responses.
While the options suggested with Pool is feasible i still prefer promises.
An example with promises is to use settle and and wait methods instead of unwrap.
The difference from the example above would be
$responses = \GuzzleHttp\Promise\settle($requestArr)->wait();
I have created a full example below for reference on how to handle the $responses too.
require __DIR__ . '/vendor/autoload.php';
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Promise as GuzzlePromise;
$client = new GuzzleClient(['timeout' => 12.0]); // see how i set a timeout
$requestPromises = [];
$sitesArray = SiteEntity->getAll(); // returns an array with objects that contain a domain
foreach ($sitesArray as $site) {
$requestPromises[$site->getDomain()] = $client->getAsync('http://' . $site->getDomain());
}
$results = GuzzlePromise\settle($requestPromises)->wait();
foreach ($results as $domain => $result) {
$site = $sitesArray[$domain];
$this->logger->info('Crawler FetchHomePages: domain check ' . $domain);
if ($result['state'] === 'fulfilled') {
$response = $result['value'];
if ($response->getStatusCode() == 200) {
$site->setHtml($response->getBody());
} else {
$site->setHtml($response->getStatusCode());
}
} else if ($result['state'] === 'rejected') {
// notice that if call fails guzzle returns is as state rejected with a reason.
$site->setHtml('ERR: ' . $result['reason']);
} else {
$site->setHtml('ERR: unknown exception ');
$this->logger->err('Crawler FetchHomePages: unknown fetch fail domain: ' . $domain);
}
$this->entityManager->persist($site); // this is a call to Doctrines entity manager
}
This example code was originally posted here.
Guzzle 6.0 has made sending multiple async requests very easy.
There are multiple ways to do it.
You can create the async requests and add the resultant promises to a single array, and get the result using the settle() method like this:
$promise1 = $client->getAsync('http://www.example.com/foo1');
$promise2 = $client->getAsync('http://www.example.com/foo2');
$promises = [$promise1, $promise2];
$results = GuzzleHttp\Promise\settle($promises)->wait();
You can now loop through these results and fetch the response using GuzzleHttpPromiseall or GuzzleHttpPromiseeach. Refer to this article for further details.
In case if you have an indeterminate number of requests to be sent(say 5 here), you can use GuzzleHttp/Pool::batch().
Here is an example:
$client = new Client();
// Create the requests
$requests = function ($total) use($client) {
for ($i = 1; $i <= $total; $i++) {
yield new Request('GET', 'http://www.example.com/foo' . $i);
}
};
// Use the Pool::batch()
$pool_batch = Pool::batch($client, $requests(5));
foreach ($pool_batch as $pool => $res) {
if ($res instanceof RequestException) {
// Do sth
continue;
}
// Do sth
}

JSON encoding headers using Sendgrid

I am trying to change the filter status for the 'subscriptiontrack' using sendgrid. I think I am sending the headers incorrectly, but not totally sure. Working inside a symfony 1.4 framework.
First I create an object of the header settings
$hdr = new SmtpApiHeader();
$hdr->addFilterSetting('subscriptiontrack', 'enable', 0);
$hdr->as_string();
which sets the filter settings and encodes the string
Then I send it off the email class
sendTestEmail::sendEmail($contents, $mailFrom, $testGroup, $subject, $hdr);
SvaSmtpApiHeader.class.php
class SmtpApiHeader
{
function addFilterSetting($filter, $setting, $value)
{
if (!isset($this->data['filters'])) {
$this->data['filters'] = array();
}
if (!isset($this->data['filters'][$filter])) {
$this->data['filters'][$filter] = array();
}
if (!isset($this->data['filters'][$filter]['settings'])) {
$this->data['filters'][$filter]['settings'] = array();
}
$this->data['filters'][$filter]['settings'][$setting] = $value;
}
function asJSON()
{
$json = json_encode($this->data);
// Add spaces so that the field can be folded
$json = preg_replace('/(["\]}])([,:])(["\[{])/', '$1$2 $3', $json);
return $json;
}
function as_string()
{
$json = $this->asJSON();
$str = "X-SMTPAPI: " . wordwrap($json, 76, "\n ");
return $str;
}
}
myEmail.class.php
<?php
class sendTestEmail
{
public static function sendEmail($contents, $mailFrom, $mailTo, $subject, $sgHeaders = null, $attachments = null)
{
try {
/*
* Load connection for mailer
*/
$connection = Swift_SmtpTransport::newInstance('smtp.sendgrid.net', 465, 'ssl')->setUsername(sfconfig::get('app_sendgrid_username'))->setPassword(sfconfig::get('app_sendgrid_password'));
// setup connection/content
$mailer = Swift_Mailer::newInstance($connection);
$message = Swift_Message::newInstance()->setSubject($subject)->setTo($mailTo);
$message->setBody($contents, 'text/html');
// if contains SMTPAPI header add it
if (null !== $sgHeaders) {
$message->getHeaders()->addTextHeader('X-SMTPAPI', $sgHeaders);
}
// update the from address line to include an actual name
if (is_array($mailFrom) and count($mailFrom) == 2) {
$mailFrom = array(
$mailFrom['email'] => $mailFrom['name']
);
}
// add attachments to email
if ($attachments !== null and is_array($attachments)) {
foreach ($attachments as $attachment) {
$attach = Swift_Attachment::fromPath($attachment['file'], $attachment['mime'])->setFilename($attachment['filename']);
$message->attach($attach);
}
}
// Send
$message->setFrom($mailFrom);
$mailer->send($message);
}
catch (Exception $e) {
throw new sfException('Error sending email out - ' . $e->getMessage());
}
}
}
The email is getting sent properly, but the unsubscribe option is still showing up at the bottom. Is this an issue with the header object or a problem with encoding for the header? Is the variable is still an object when getting added to the headers?
You're misunderstanding how JSON encoding works. Let's take a look at your as_string method:
function as_string()
{
$json = $this->asJSON();
$str = "X-SMTPAPI: " . wordwrap($json, 76, "\n ");
return $str;
}
This would output something to the effect of:
X-SMTPAPI: { "filters": { "subscriptiontrack": { "settings": { "enable": 0 } } } }
You should note that this isn't valid JSON because it is prefixed with "X-SMTPAPI". Instead, you should be calling asJSON, but SwiftMailer doesn't know that.
Try switching the header line to:
$message->getHeaders()->addTextHeader('X-SMTPAPI', $sgHeaders->asJSON());
If that doesn't work, can you give us a dump of:
$headers = $message->getHeaders();
echo $headers->toString();
And have you thought about using the official PHP library instead? https://github.com/sendgrid/sendgrid-php

PHP+SoapClient exceptions and headers? (UPS Rating)

I'm trying to use PHP and SoapClient to utilize the UPS Ratings web service. I found a nice tool called WSDLInterpreter to create a starting point library for creating the service requests, but regardless what I try I keep getting the same (non-descriptive) error:
EXCEPTION=SoapFault::__set_state(array(
'message' => 'An exception has been raised as a result of client data.',
'string' => '',
'code' => 0,
Um ok, what the hell does this mean?
Unlike some of the other web service tools I have implemented, the UPS Soap wants a security block put into the header. I tried doing raw associative array data but I wasn't sure 100% if I was injecting the header part correctly.
Using the WSDLInterpreter, it pulls out a RateService class with a ProcessRate method that excepts it's own (instance) datastructure for the RateRequest and UPSSecurity portions, but all of the above generates that same error.
Here's a sample of the code that I'm using that calls classes defined by the interpreter:
require_once "Shipping/UPS/RateService.php"; // WSDLInterpreter class library
class Query
{
// constants removed for stackoverflow that define (proprietary) security items
private $_upss;
private $_shpr;
// use Interpreter code's RateRequest class to send with ProcessRate
public function soapRateRequest(RateRequest $req, UPSSecurity $upss = null)
{
$res = false;
if (!isset($upss)) {
$upss = $this->__getUPSS();
}
echo "SECURITY:\n" . var_export($upss, 1) . "\n";
$upsrs = new RateService(self::UPS_WSDL);
try {
$res = $upsrs->ProcessRate($req, $upss);
} catch (SoapFault $exception) {
echo 'EXCEPTION=' . var_export($exception, 1) . "\n";
}
return $res;
}
// create a soap data structure to send to web service from shipment
public function getRequestSoap(Shipment $shpmnt)
{
$qs = new RateRequest();
// pickup information
$qs->PickupType->Code = '01';
$qs->Shipment->Shipper = $this->__getAcctInfo();
// Ship To address
$qs->Shipment->ShipTo->Address->AddressLine = $this->__getAddressArray($shpmnt->destAddress->address1, $shpmnt->destAddress->address2);
$qs->Shipment->ShipTo->Address->City = $shpmnt->destAddress->city;
$qs->Shipment->ShipTo->Address->StateProvinceCode = $shpmnt->destAddress->state;
$qs->Shipment->ShipTo->Address->PostalCode = $shpmnt->destAddress->zip;
$qs->Shipment->ShipTo->Address->CountryCode = $shpmnt->destAddress->country;
$qs->Shipment->ShipTo->Name = $shpmnt->destAddress->name;
// Ship From address
$qs->Shipment->ShipFrom->Address->AddressLine = $this->__getAddressArray($shpmnt->origAddress->address1, $shpmnt->origAddress->address2);
$qs->Shipment->ShipFrom->Address->City = $shpmnt->origAddress->city;
$qs->Shipment->ShipFrom->Address->StateProvinceCode = $shpmnt->origAddress->state;
$qs->Shipment->ShipFrom->Address->PostalCode = $shpmnt->origAddress->zip;
$qs->Shipment->ShipFrom->Address->CountryCode = $shpmnt->origAddress->country;
$qs->Shipment->ShipFrom->Name = $shpmnt->origAddress->name;
// Service type
// TODO cycle through available services
$qs->Shipment->Service->Code = "03";
$qs->Shipment->Service->Description = "UPS Ground";
// Package information
$pkg = new PackageType();
$pkg->PackagingType->Code = "02";
$pkg->PackagingType->Description = "Package/customer supplied";
// dimensions
$pkg->Dimensions->UnitOfMeasurement->Code = $shpmnt->dimensions->dimensionsUnit;
$pkg->Dimensions->Length = $shpmnt->dimensions->length;
$pkg->Dimensions->Width = $shpmnt->dimensions->width;
$pkg->Dimensions->Height = $shpmnt->dimensions->height;
$pkg->PackageServiceOptions->DeclaredValue->CurrencyCode = "USD";
$pkg->PackageServiceOptions->DeclaredValue->MonetaryValue = $shpmnt->dimensions->value;
$pkg->PackageServiceOptions->DeclaredValue->CurrencyCode = "USD";
$pkg->PackageWeight->UnitOfMeasurement = $this->__getWeightUnit($shpmnt->dimensions->weightUnit);
$pkg->PackageWeight->Weight = $shpmnt->dimensions->weight;
$qs->Shipment->Package = $pkg;
$qs->CustomerClassification->Code = 123456;
$qs->CustomerClassification->Description = "test_rate_request";
return $qs;
}
// fill out and return a UPSSecurity data structure
private function __getUPSS()
{
if (!isset($this->_upss)) {
$unmt = new UsernameToken();
$unmt->Username = self::UPSS_USERNAME;
$unmt->Password = self::UPSS_PASSWORD;
$sat = new ServiceAccessToken();
$sat->AccessLicenseNumber = self::UPSS_ACCESS_LICENSE_NUMBER;
$upss = new UPSSecurity();
$upss->UsernameToken = $unmt;
$upss->ServiceAccessToken = $sat;
$this->_upss = $upss;
}
return $this->_upss;
}
// get our shipper/account info (some items blanked for stackoverflow)
private function __getAcctInfo()
{
if (!isset($this->_shpr)) {
$shpr = new ShipperType();
$shpr->Address->AddressLine = array(
"CONTACT",
"STREET ADDRESS"
);
$shpr->Address->City = "CITY";
$shpr->Address->StateProvinceCode = "MI";
$shpr->Address->PostalCode = "ZIPCODE";
$shpr->Address->CountryCode = "US";
$shpr = new ShipperType();
$shpr->Name = "COMPANY NAME";
$shpr->ShipperNumber = self::UPS_ACCOUNT_NUMBER;
$shpr->Address = $addr;
$this->_shpr = $shpr;
}
return $this->_shpr;
}
private function __getAddressArray($adr1, $adr2 = null)
{
if (isset($adr2) && $adr2 !== '') {
return array($adr1, $adr2);
} else {
return array($adr1);
}
}
}
It doesn't even seem to be getting to the point of sending anything over the Soap so I am assuming it is dying as a result of something not matching the WSDL info. (keep in mind, I've tried sending just a properly seeded array of key/value details to a manually created SoapClient using the same WSDL file with the same error resulting)
It would just be nice to get an error to let me know what about the 'client data' is a problem. This PHP soap implementation isn't impressing me!
I know this answer is probably way too late, but I'll provide some feedback anyway. In order to make a custom SOAP Header you'll have to override the SoapHeader class.
/*
* Auth Class to extend SOAP Header for WSSE Security
* Usage:
* $header = new upsAuthHeader($user, $password);
* $client = new SoapClient('{...}', array("trace" => 1, "exception" => 0));
* $client->__setSoapHeaders(array($header));
*/
class upsAuthHeader extends SoapHeader
{
...
function __construct($user, $password)
{
// Using SoapVar to set the attributes has proven nearly impossible; no WSDL.
// It might be accomplished with a combined SoapVar and stdClass() approach.
// Security Header - as a combined XSD_ANYXML SoapVar
// This method is much easier to define all of the custom SoapVars with attributes.
$security = '<ns2:Security xmlns:ns2="'.$this->wsse.'">'. // soapenv:mustUnderstand="1"
'<ns2:UsernameToken ns3:Id="UsernameToken-49" xmlns:ns3="'.$this->wsu.'">'.
'<ns2:Username>'.$user.'</ns2:Username>'.
'<ns2:Password Type="'.$this->password_type.'">'.htmlspecialchars($password).'</ns2:Password>'.
'</ns2:UsernameToken>'.
'</ns2:Security>';
$security_sv = new SoapVar($security, XSD_ANYXML);
parent::__construct($this->wsse, 'Security', $security_sv, false);
}
}
Then call the upsAuthHeader() before the soap call.
$client = new SoapClient($this->your_ups_wsdl,
array('trace' => true,
'exceptions' => true,
'soap_version' => SOAP_1_1
)
);
// Auth Header - Security Header
$header = new upsAuthHeader($user, $password);
// Set Header
$client->__setSoapHeaders(array($header));

OpenID Discovery Methods - Yadis VS HTML

Recently, I've begun writing my own PHP OpenID consumer class in order to better understand openID. As a guide, I've been referencing the [LightOpenID Class][1]. For the most part, I understand the code and how OpenID works. My confusion comes when looking at the author's discover function:
function discover($url)
{
if(!$url) throw new ErrorException('No identity supplied.');
# We save the original url in case of Yadis discovery failure.
# It can happen when we'll be lead to an XRDS document
# which does not have any OpenID2 services.
$originalUrl = $url;
# A flag to disable yadis discovery in case of failure in headers.
$yadis = true;
# We'll jump a maximum of 5 times, to avoid endless redirections.
for($i = 0; $i < 5; $i ++) {
if($yadis) {
$headers = explode("\n",$this->request($url, 'HEAD'));
$next = false;
foreach($headers as $header) {
if(preg_match('#X-XRDS-Location\s*:\s*(.*)#', $header, $m)) {
$url = $this->build_url(parse_url($url), parse_url(trim($m[1])));
$next = true;
}
if(preg_match('#Content-Type\s*:\s*application/xrds\+xml#i', $header)) {
# Found an XRDS document, now let's find the server, and optionally delegate.
$content = $this->request($url, 'GET');
# OpenID 2
# We ignore it for MyOpenID, as it breaks sreg if using OpenID 2.0
$ns = preg_quote('http://specs.openid.net/auth/2.0/');
if (preg_match('#<Service.*?>(.*)<Type>\s*'.$ns.'(.*?)\s*</Type>(.*)</Service>#s', $content, $m)
&& !preg_match('/myopenid\.com/i', $this->identity)) {
$content = $m[1] . $m[3];
if($m[2] == 'server') $this->identifier_select = true;
$content = preg_match('#<URI>(.*)</URI>#', $content, $server);
$content = preg_match('#<LocalID>(.*)</LocalID>#', $content, $delegate);
if(empty($server)) {
return false;
}
# Does the server advertise support for either AX or SREG?
$this->ax = preg_match('#<Type>http://openid.net/srv/ax/1.0</Type>#', $content);
$this->sreg = preg_match('#<Type>http://openid.net/sreg/1.0</Type>#', $content);
$server = $server[1];
if(isset($delegate[1])) $this->identity = $delegate[1];
$this->version = 2;
$this->server = $server;
return $server;
}
# OpenID 1.1
$ns = preg_quote('http://openid.net/signon/1.1');
if(preg_match('#<Service.*?>(.*)<Type>\s*'.$ns.'\s*</Type>(.*)</Service>#s', $content, $m)) {
$content = $m[1] . $m[2];
$content = preg_match('#<URI>(.*)</URI>#', $content, $server);
$content = preg_match('#<.*?Delegate>(.*)</.*?Delegate>#', $content, $delegate);
if(empty($server)) {
return false;
}
# AX can be used only with OpenID 2.0, so checking only SREG
$this->sreg = preg_match('#<Type>http://openid.net/sreg/1.0</Type>#', $content);
$server = $server[1];
if(isset($delegate[1])) $this->identity = $delegate[1];
$this->version = 1;
$this->server = $server;
return $server;
}
$next = true;
$yadis = false;
$url = $originalUrl;
$content = null;
break;
}
}
if($next) continue;
# There are no relevant information in headers, so we search the body.
$content = $this->request($url, 'GET');
if($location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'value')) {
$url = $this->build_url(parse_url($url), parse_url($location));
continue;
}
}
if(!$content) $content = $this->request($url, 'GET');
# At this point, the YADIS Discovery has failed, so we'll switch
# to openid2 HTML discovery, then fallback to openid 1.1 discovery.
$server = $this->htmlTag($content, 'link', 'rel', 'openid2.provider', 'href');
$delegate = $this->htmlTag($content, 'link', 'rel', 'openid2.local_id', 'href');
$this->version = 2;
# Another hack for myopenid.com...
if(preg_match('/myopenid\.com/i', $server)) {
$server = null;
}
if(!$server) {
# The same with openid 1.1
$server = $this->htmlTag($content, 'link', 'rel', 'openid.server', 'href');
$delegate = $this->htmlTag($content, 'link', 'rel', 'openid.delegate', 'href');
$this->version = 1;
}
if($server) {
# We found an OpenID2 OP Endpoint
if($delegate) {
# We have also found an OP-Local ID.
$this->identity = $delegate;
}
$this->server = $server;
return $server;
}
throw new ErrorException('No servers found!');
}
throw new ErrorException('Endless redirection!');
}
[1]: http://gitorious.org/lightopenid
Okay, Here's the logic as I understand it (basically):
Check to see if the $url sends you a valid XRDS file that you then parse to figure out the OpenID provider's endpoint.
From my understanding, this is called the Yadis authentication method.
If no XRDS file is found, Check the body of the response for an HTML <link> tag that contains the url of the endpoint.
What. The. Heck.
I mean seriously? Essentially screen scrape the response and hope you find a link with the appropriate attribute value?
Now, don't get me wrong, this class works like a charm and it's awesome. I'm just failing to grok the two separate methods used to discover the endpoint: XRDS (yadis) and HTML.
My Questions
Are those the only two methods used in the discovery process?
Is one only used in version 1.1 of OpenID and the other in version 2?
Is it critical to support both methods?
The site I've encountered the HTML method on is Yahoo. Are they nuts?
Thanks again for your time folks. I apologize if I sound a little flabbergasted, but I was genuinely stunned at the methodology once I began to understand what measures were being taken to find the endPoint.
Specification is your friend.
But answering your question:
Yes. Those are the only two methods defined by the OpenID specifications (at least, for URLs -- there is a third method for XRIs).
No, both can be used with both version of the protocol. Read the function carefully, and you'll see that it supports both methods for both versions.
If you want your library to work with every provider and user, you'd better do. Some users paste the HTML tags into their sites, so their site's url can be used as an openid.
Some providers even use both methods at once, to mantain compatibility with consumers not implementing YADIS discovery (which isn't part of OpenID 1.1, but can be used with it). So that does make sense.
And yes, HTML discovery is about searching for a <link> in the response body. That's why it's called HTML discovery.

Categories