Reminiscence

/* the tucows developer blog */

XCP Commands via XML Over HTTPS POST

The Preferred Way

A yak, shaving.After having a discussion with Grant Spradling, our Product Manager for Platypus and Enablers, it was decided that we’d promote the XML over HTTPS POST method as the preferred way for client code to connect to OpenSRS.

It’s much closer to the way most web services work these days, and it’s simpler than the original method, which required you to get your paws on a Blowfish library and do some extra programming or use a library like OpenSRS-PHP. As I like to put it, connecting to OpenSRS using the XML over HTTPS POST method involves “a lot less yak shaving“: It’s the method that requires the least setting up, it uses libraries that are very likely to be in the default installation of your favorite programming language and it minimizes the amount of code you have to write.

What Does This Mean?

Project Ash: Wrapper Functions for Tucows APIsIf you’ve been following the Project Ash series of articles, you’re probably wondering what this change means.

First, any example code I’ve shown and any code you’ve written based on those examples will still work. We have too many customers whose client code connects to the OpenSRS the old way to invalidate it. If you want to, you’ll still be able to connect to OpenSRS the old way; we just think that connecting using XML over HTTPS POST is the better way.

Second, you might be wondering if this change invalidates any of the existing Project Ash articles. The answer is “no, for the most part”:

  • The API calls will remain the same. What changes is the way in which those commands are sent.
  • The XML for API calls will remain the same. Once again, ti’s not the commands that have changed, but the way in which they’re sent.
  • The PHP example code will still work. It’s just that we think there’s a better way, and I’ll be writing new PHP examples in the coming days to reflect it.

What This Article Will Cover

In this article, I’m going to:

  • Look at the makeup of an API message sent via HTTPS POST
  • Go over code examples in PHP and Ruby. In follow-up articles, I’ll provide code examples in other programming languages.

Sending API Commands Via HTTPS POST

HTTP POST: A Quick Refresher

HTTPS isn’t really a protocol separate from HTTP, but HTTP over a secure connection, either Secure Sockets Layer (SSL) or Transport Layer Security (TLS). The protocol itself is the same, whether it’s HTTP or HTTPS.

The two most common “verbs” in the HTTP protocol are GET and POST.

GET

GET is dirt simple — everything is specified in the URL and headers. Here’s an example GET request that results from a login form at the root of catmas.com where the user entered a username of chunkylover53 and the password donuts and clicked a submit button labelled OK:


GET /?username=chunkylover53&password=donuts&submit=OK HTTP/1.1
Host: www.catmas.com
User-Agent: Mozilla/5.0

Note that in a GET request, information specific to the application (in this case, the username, login and peripherally, the text on the “submit” button) is embedded in the URL. Although there’s no technical limit to the length of an URL, URL length is limited by the browser; a long-time general rule is not to make URLs longer than 1K characters.

POST

POST is only a little more complex. A POST request still has an URL and headers, but also has additional information. Here’s the same form request as the one shown in the GET example above, but in POST form:


POST / HTTP/1.1
Host: www.catmas.com
User-Agent: Mozilla/5.0
Content-Length: 48
Content-Type: application/x-www-form-urlencoded

username=chunkylover53&password=donuts&submit=OK

In a POST request, information specific to the application is not in the URL; rather, it comes after the headers and is separated from the headers by a blank line. Note that you have to provide the length (in bytes) of the information you embed in the request in the Content-Length: header.

If the POST request was generated by a form, the information is typically in “URL-encoded form” format, like this:

username=chunkylover53&password=donuts&submit=OK

There’s no reason that the information embedded in a POST request can’t be in other forms. This flexibility gave rise to sending XML in HTTP POST requests, which in turn gave us AJAX — and XML over HTTPS POST.

One more thing: in the case of sending messages to OpenSRS, it’s done on port 55443.

Headers for an XCP Command Sent via HTTPS POST

Let’s get the hard stuff out of the way first. The table below shows what the headers for an XCP command sent via HTTPS POST should be.

Header Value
Content-Type
Wooden pegs-and-holes toy
Since we’re sending an XML message inside the POST request, you should set this to text/xml.
X-Username
'Hello my name is...' sticker
Set this to your Tucows API username.
X-Signature
Pen and signature
Set this to the message digest, a value calculated using the XCP API message you want to send and your private key.

Here’s a diagram that shows how to generate the message digest:

Diagram showing how to generate a message digest

In PHP, the implementation looks like this:

$signature = md5(md5($apiCommand . OPENSRS_PRIVATE_KEY) . $OPENSRS_PRIVATE_KEY);

Here’s the same thing in Ruby:

signature = Digest::MD5.hexdigest(
	Digest::MD5.hexdigest(api_command + OPENSRS_PRIVATE_KEY) +
	OPENSRS_PRIVATE_KEY
)
Content-Length
Tape measure
Set this to the length (in bytes) of XCP API command that you’re sending:
  • In PHP, use the strlen() function to get the length of your XCP API command.
  • In Ruby, use the length or size method to get the length of your XCP API command.

Body for an XCP Command Sent via HTTPS POST

Message in a bottleThe message body is easy: it’s just the XML that makes up the API command.

For example, if you wanted to run the lookup command on the domain catmas.com, the body would simply be:


<?xml version="1.0" encoding="UTF-8" standalone='yes'?>
<!DOCTYPE OPS_envelope SYSTEM 'ops.dtd'>
<OPS_envelope>
 <header>
   <version>0.9</version>
 </header>
 <body>
   <data_block>
     <dt_assoc>
       <item key="protocol">xcp</item>
       <item key="action">lookup</item>
       <item key="object">domain</item>
       <item key="attributes">
         <dt_assoc>
           <item key="domain">catmas.com</item>
         </dt_assoc>
       </item>
     </dt_assoc>
   </data_block>
 </body>
</OPS_envelope>

Let’s Code!

And finally, I’ll finish by showing you some code. Here’s the code for a simple application that runs the lookup command on the domain catmas.com, in both PHP and Ruby. Tune in next week, when I’ll post code in other programming languages.

PHP


<html>
<body>
<?php
$username = "INSERT YOUR USERNAME HERE";
$private_key = "INSERT YOUR PRIVATE KEY HERE"; 

$xml = '<?xml version='1.0' encoding="UTF-8" standalone="no" ?>
<!DOCTYPE OPS_envelope SYSTEM "ops.dtd">
<OPS_envelope>
 <header>
  <version>0.9</version>
  </header>
 <body>
  <data_block>
   <dt_assoc>
    <item key="object">DOMAIN</item>
    <item key="action">LOOKUP</item>
    <item key="protocol">XCP</item>
    <item key="attributes">
     <dt_assoc>
      <item key="domain">catmas.com</item>
     </dt_assoc>
    </item>
   </dt_assoc>
  </data_block>
 </body>
</OPS_envelope>'; 

$signature = md5(md5($xml.$private_key).$private_key);
$host = "rr-n1-tor.opensrs.net"; # Set this to horizon.opensrs.net if you
                                 # want to connect to the test server instead.
$port = 55443;
$url = "/";
$header = "";
$header .= "POST $url HTTP/1.0rn";
$header .= "Content-Type: text/xmlrn";
$header .= "X-Username: " . $username . "rn";
$header .= "X-Signature: " . $signature . "rn";
$header .= "Content-Length: " . strlen($xml) . "rnrn";
# ssl:// requires OpenSSL to be installed
$fp = fsockopen ("ssl://$host", $port, $errno, $errstr, 30); 

echo "<pre>"; 

if (!$fp) {
  print "HTTP ERROR!";
} else {
  # post the data to the server
  fputs ($fp, $header . $xml);
  while (!feof($fp)) {
    $res = fgets ($fp, 1024);
    echo htmlEntities($res);
  }
  fclose ($fp);
} 

echo "</pre>";
?>
</body>

Ruby


#! /usr/bin/env ruby

require 'net/https'
require 'digest/md5'
require 'rexml/document'
include REXML

OPENSRS_SERVER = 'rr-n1-tor.opensrs.net' # Set this to horizon.opensrs.net if you
                                         # want to connect to the test server instead.
OPENSRS_PORT = 55443
OPENSRS_USERNAME = 'INSERT YOUR USERNAME HERE'
OPENSRS_PRIVATE_KEY = 'INSERT YOUR PRIVATE KEY HERE'

domain = ARGV[0]

requestXml = <<-END
<?xml version="1.0" encoding="UTF-8" standalone='yes'?>
<!DOCTYPE OPS_envelope SYSTEM 'ops.dtd'>
<OPS_envelope>
 <header>
   <version>0.9</version>
 </header>
 <body>
   <data_block>
     <dt_assoc>
       <item key="protocol">xcp</item>
       <item key="action">lookup</item>
       <item key="object">domain</item>
       <item key="attributes">
         <dt_assoc>
           <item key="domain">catmas.com</item>
         </dt_assoc>
       </item>
     </dt_assoc>
   </data_block>
 </body>
</OPS_envelope>
END

message_digest = Digest::MD5.hexdigest(
                   Digest::MD5.hexdigest(requestXml + OPENSRS_PRIVATE_KEY) +
                   OPENSRS_PRIVATE_KEY
                 )

http = Net::HTTP.new OPENSRS_SERVER, OPENSRS_PORT

begin
  http.use_ssl = true
  req = Net::HTTP::Post.new('/')
  req['Content-Type'] = 'text/xml'
  req['X-Username'] = OPENSRS_USERNAME
  req['X-Signature'] = message_digest
  req['Content-Length'] = requestXml.size.to_s

  response = http.request(req, requestXml)

ensure
  http.finish if http.started?

end

puts response.body
신고

댓글 0개가 달렸습니다.