Skip to content

Commit

Permalink
converted README from googlecode wiki format to GitHub markdown forma…
Browse files Browse the repository at this point in the history
…t and moved sources to src/ dir
  • Loading branch information
izuzak committed Nov 4, 2010
1 parent 69eecb4 commit ffe0984
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 0 deletions.
13 changes: 13 additions & 0 deletions LICENSE
@@ -0,0 +1,13 @@
Copyright 2010 Ivan Zuzak.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
125 changes: 125 additions & 0 deletions README.md
@@ -0,0 +1,125 @@
URLecho
=======

Protocol and AppEngine service for echoing a HTTP response defined in the request URL

Overview
--------

URL echo is a HTTP-based client-server protocol for defining the HTTP response to a HTTP GET request within the request itself. The client sends JSON-formatted and URL-encoded parameters of the response that the server should return within the URL of a HTTP GET request. The server responds with a HTTP response by mirroring the parameters defined in the URL of the request thus effectively echoing the client-defined response. See the "Protocol" section for more information.

The project also provides a reference implementation of an URL echo server. The server is implemented as public, open-source and free to use AppEngine service. See the "AppEngine server" section for more information.

Purpose
-------

URL echo enables dynamic hosting of HTTP resources within a URL without need for any physical infrastructure (e.g. web servers). The reason for hosting a resource within a URL are links, the glue of the Web. A link functions as a pointer to a web resource - it is a single entity which defines both which resource should be fetched (a URL identifier of the resource) and how the resource should be fetched (HTTP GET request to that URL). Therefore, defining a resource in the URL of a HTTP request makes it widely usable in the existing web architecture by both developers and end-users.

Want to show a web page to someone? With URL echo you wouldn't need to setup a web hosting account with a provider, upload your files to a web host and then share the URL to the web page with your friends. Instead, you would build your web page, encode it's content in a URL, and share the URL with your friends skipping the whole step of hosting. Need a fast way to create a large number of HTTP resources, for testing or other purposes? Just create the URLs containing the resources on the client side, no need for creating server-side scripts.

Since the resource are defined and contained solely within the URL, URL echo may only be used to host static HTTP resources. However, a significant percentage of all web resources are static resources and in many occasions that's enough. Also, since creating a resource is as easy as constructing a URL, this limitation is irrelevant in use cases where the resource creator has control over all links that point to that URL - a resource is changed by pointing a link at a different URL can easily change the URL of the link to change the resource.

Protocol
--------

The URL echo protocol is an extension of the [HTTP protocol](http://tools.ietf.org/html/rfc2616), specifically the HTTP GET method. With regular HTTP, when a server receives a GET request from the client, it responds with a response containing the URL-addressed resource. In most cases, this means that the resource is retreived either from a server-side script or fetched from a static file on the server.

In contrast, the URL echo protocol functions as follows:

* **The client** sends a HTTP GET request to the server. The URL of the request contains a [JSON-formatted](http://json.org) and [URL-encoded](http://en.wikipedia.org/wiki/Percent-encoding) representation of a HTTP response object. The exact positioning of the JSON-formatted HTTP response object is up to the server implementation (e.g. as a query parameter or a part of the path).

The HTTP response JSON object has 3 properties:
* status - a string representing the HTTP status code of the HTTP response
* headers - a JSON object representing headers of the HTTP response. Each header is represented as key-value string pair of a header name and header value.
* content - a string representing the body of the HTTP response

All properties are optional, and in the case that any property is omitted - the server will assume default values as defined below.

Here's an example JSON object that defines a HTTP response containing an ATOM feed in the response body:

> {
> "status" : "200",
> "headers" : { "Content-Type" : "application/atom+xml" }
> "content" : "
> <?xml version='1.0' encoding='utf-8'?>
> <feed xmlns='http://www.w3.org/2005/Atom'>
> <title>Example Feed</title>
> <subtitle>A subtitle.</subtitle>
> <link href='http://example.org/feed/' rel='self' />
> <link href='http://example.org/' />
> <id>urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6</id>
> <updated>2003-12-13T18:30:02Z</updated>
> <author>
> <name>John Doe</name>
> <email>johndoe@example.com</email>
> </author>
> <entry>
> <title>Atom-Powered Robots Run Amok</title>
> <link href='http://example.org/2003/12/13/atom03' />
> <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
> <updated>2003-12-13T18:30:02Z</updated>
> <summary>Some text.</summary>
> </entry>
> </feed>"
> }
> And this is the URL-encoded string for the same JSON-formatted object:
> %7B%22status%22:200,%22headers%22:%7B%22Content-Type%22:%22application/atom%2Bxml%22%7D,%22content%22:%22%3C%3Fxml%20version%3D'1.0'%20encoding%3D'utf-8'%3F%3E%5Cn%20%20%20%20%20%20%3Cfeed%20xmlns%3D'http://www.w3.org/2005/Atom'%3E%5Cn%20%20%20%20%20%20%20%20%3Ctitle%3EExample%20Feed%3C/title%3E%5Cn%20%20%20%20%20%20%20%20%3Csubtitle%3EA%20subtitle.%3C/subtitle%3E%5Cn%20%20%20%20%20%20%20%20%3Clink%20href%3D'http://example.org/feed/'%20rel%3D'self'%20/%3E%5Cn%20%20%20%20%20%20%20%20%3Clink%20href%3D'http://example.org/'%20/%3E%5Cn%20%20%20%20%20%20%20%20%3Cid%3Eurn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6%3C/id%3E%5Cn%20%20%20%20%20%20%20%20%3Cupdated%3E2003-12-13T18:30:02Z%3C/updated%3E%5Cn%20%20%20%20%20%20%20%20%3Cauthor%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Cname%3EJohn%20Doe%3C/name%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Cemail%3Ejohndoe@example.com%3C/email%3E%5Cn%20%20%20%20%20%20%20%20%3C/author%3E%5Cn%20%20%20%20%20%20%20%20%3Centry%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Ctitle%3EAtom-Powered%20Robots%20Run%20Amok%3C/title%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Clink%20href%3D'http://example.org/2003/12/13/atom03'%20/%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Cid%3Eurn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a%3C/id%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Cupdated%3E2003-12-13T18:30:02Z%3C/updated%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Csummary%3ESome%20text.%3C/summary%3E%5Cn%20%20%20%20%20%20%20%20%3C/entry%3E%3C/feed%3E%22%7D

* **The server** receives the request, and retrieves the URL-encoded JSON-formatted JSON object from the request URL. The server then replies by copying HTTP response parameters from the retreived object defined object, thus echoing the HTTP response object defined in the request URL.

The server must construct the response the following way:
* The status of the HTTP response must be copied from the `status` parameter. In case that the `status` parameter is not present, the server must default the response status to "200".
* Headers defined in the `headers` parameter must be copied to the headers of the server's HTTP response. Headers which are not set may be set arbitrarily by the server. In case that the `headers` parameter is not present, the server must set the "Content-Type" header to "text/html" representing the a plain HTML content type.
* The body of the HTTP response must be copied from the `content` parameter. In case that the `content` parameter is not present, the server must default the response body to an empty string.

Here's the URL echo HTTP response for the above ATOM feed GET example:

> HTTP/1.1 200 OK
> Content-Type: application/atom+xml
> <...other-server-headers...>
>
> <?xml version='1.0' encoding='utf-8'?>
> <feed xmlns='http://www.w3.org/2005/Atom'>
> <title>Example Feed</title>
> <subtitle>A subtitle.</subtitle>
> <link href='http://example.org/feed/' rel='self' />
> <link href='http://example.org/' />
> <id>urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6</id>
> <updated>2003-12-13T18:30:02Z</updated>
> <author>
> <name>John Doe</name>
> <email>johndoe@example.com</email>
> </author>
> <entry>
> <title>Atom-Powered Robots Run Amok</title>
> <link href='http://example.org/2003/12/13/atom03' />
> <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
> <updated>2003-12-13T18:30:02Z</updated>
> <summary>Some text.</summary>
> </entry>
> </feed>
AppEngine server
----------------

A public, free to use and [open-source](http://github.com/izuzak/urlecho/tree/master/src) implementation of an URL echo server is available on [AppEngine](http://code.google.com/appengine/). The use the server, build an URL-encoded JSON-formatted string as defined in the protocol section, and pass it as an URL query parameter named **`jsonResponse`** to **`http://urlecho.appspot.com/echo`**. The resulting URL should look like this:

> http://urlecho.appspot.com/echo?jsonResponse=URL_ENCODED_JSON
Here's the URL for the above ATOM feed GET example, which you can copy and paste into a new tab to see it work:

> http://urlecho.appspot.com/echo?jsonResponse=%7B%22status%22:200,%22headers%22:%7B%22Content-Type%22:%22application/atom%2Bxml%22%7D,%22content%22:%22%3C%3Fxml%20version%3D'1.0'%20encoding%3D'utf-8'%3F%3E%5Cn%20%20%20%20%20%20%3Cfeed%20xmlns%3D'http://www.w3.org/2005/Atom'%3E%5Cn%20%20%20%20%20%20%20%20%3Ctitle%3EExample%20Feed%3C/title%3E%5Cn%20%20%20%20%20%20%20%20%3Csubtitle%3EA%20subtitle.%3C/subtitle%3E%5Cn%20%20%20%20%20%20%20%20%3Clink%20href%3D'http://example.org/feed/'%20rel%3D'self'%20/%3E%5Cn%20%20%20%20%20%20%20%20%3Clink%20href%3D'http://example.org/'%20/%3E%5Cn%20%20%20%20%20%20%20%20%3Cid%3Eurn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6%3C/id%3E%5Cn%20%20%20%20%20%20%20%20%3Cupdated%3E2003-12-13T18:30:02Z%3C/updated%3E%5Cn%20%20%20%20%20%20%20%20%3Cauthor%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Cname%3EJohn%20Doe%3C/name%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Cemail%3Ejohndoe@example.com%3C/email%3E%5Cn%20%20%20%20%20%20%20%20%3C/author%3E%5Cn%20%20%20%20%20%20%20%20%3Centry%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Ctitle%3EAtom-Powered%20Robots%20Run%20Amok%3C/title%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Clink%20href%3D'http://example.org/2003/12/13/atom03'%20/%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Cid%3Eurn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a%3C/id%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Cupdated%3E2003-12-13T18:30:02Z%3C/updated%3E%5Cn%20%20%20%20%20%20%20%20%20%20%3Csummary%3ESome%20text.%3C/summary%3E%5Cn%20%20%20%20%20%20%20%20%3C/entry%3E%3C/feed%3E%22%7D


You can also use the simple URL builder form available at: http://izuzak.github.com/urlecho.

Advanced
--------

### URL shortening ###

URL echo URLs are long and ugly since they contain a complete web resource definition (e.g. a web page). Although the URLs are completely functional, sometims it is more practical to have a shorter version. This is a perfect use case for URL shortening services like [bit.ly](http://bit.ly). URL shortening services produce short URLs from long ones and sending a GET request to short URL redirects the request to the long URL.

For example, this is the short bit.ly version of the ATOM feed URL echo example URL: http://bit.ly/bV0So.
16 changes: 16 additions & 0 deletions src/app.yaml
@@ -0,0 +1,16 @@
application: urlecho
version: 1
runtime: python
api_version: 1

handlers:
- url: /favicon.ico
static_files: static/favicon.ico
upload: static/favicon.ico

- url: /robots.txt
static_files: static/robots.txt
upload: static/robots.txt

- url: (.*)
script: urlecho.py
11 changes: 11 additions & 0 deletions src/index.yaml
@@ -0,0 +1,11 @@
indexes:

# AUTOGENERATED

# This index.yaml is automatically updated whenever the dev_appserver
# detects that a new type of query is run. If you want to manage the
# index.yaml file manually, remove the above marker line (the line
# saying "# AUTOGENERATED"). If you want to manage some indexes
# manually, move them above the marker line. The index.yaml file is
# automatically uploaded to the admin console when you next deploy
# your application using appcfg.py.
Binary file added src/static/favicon.ico
Binary file not shown.
2 changes: 2 additions & 0 deletions src/static/robots.txt
@@ -0,0 +1,2 @@
User-agent: *
Disallow: /
144 changes: 144 additions & 0 deletions src/urlecho.py
@@ -0,0 +1,144 @@
import cgi
import os
import re
import urllib
import urlparse
import mimetypes

from django.utils import simplejson
from google.appengine.api import urlfetch
from google.appengine.api.urlfetch import DownloadError
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app

class BaseURLEchoHandler(webapp.RequestHandler):
def parseRequestQueryStringParams(self, requestQueryString, paramsList):
requestQueryString = "&" + requestQueryString
paramIndexes = [(param, requestQueryString.find('&' + param + '=')) for param in paramsList]
paramIndexes = sorted(filter(lambda x: x[1]!=-1, paramIndexes),key=lambda x:x[1])
paramIndexes = [(paramIndexes[i][0], paramIndexes[i][1] + len(paramIndexes[i][0]) + 2, len(requestQueryString) if (i == (len(paramIndexes)-1)) else paramIndexes[i+1][1])
for i in range(len(paramIndexes))]
return dict((param[0], urllib.unquote(requestQueryString[param[1]:param[2]])) for param in paramIndexes)

def options(self):
if self.request.headers.has_key('Access-Control-Request-Method'):
self.response.set_status(200)
self.response.headers['Access-Control-Allow-Origin'] = self.request.headers['Origin']
self.response.headers['Access-Control-Max-Age'] = 3600
self.response.headers['Access-Control-Allow-Methods'] = self.request.headers['Access-Control-Request-Method']
else:
self.processRequest()
return

def get(self):
self.processRequest()
return

def put(self):
self.processRequest()
return

def post(self):
self.processRequest()
return

def delete(self):
self.processRequest()
return

def head(self):
self.processRequest()
return

def processRequest(self):
responseParams, isDebugMode = self.parseResponseParams(self.request.query_string)

# cache all results if not requested otherwise
if not responseParams.has_key('Cache-Control'):
self.response.headers['Cache-Control'] = 'max-age=3600'

# process debug mode
if isDebugMode:
debugHeaders = {}
debugHeaders.update(self.response.headers)
if responseParams.has_key('headers'):
debugHeaders.update(responseParams['headers'])
self.response.headers['Content-Type'] = 'text'
self.response.out.write("Request received:\n%s\n\n" % self.request.url)
self.response.out.write("Status code:\n%s\n\n" % (str(responseParams['status']) if responseParams.has_key('status') else "200"))
self.response.out.write("Headers:\n%s\n\n" % "\n".join( item[0] + ": " + item[1] for item in debugHeaders.items()))
self.response.out.write("Content:\n%s" % responseParams['content'])
else:
# process status
if responseParams.has_key('status'):
self.response.set_status(responseParams['status'])

# process headers
if responseParams.has_key('headers'):
for headerName in responseParams['headers'].keys():
self.response.headers[headerName] = responseParams['headers'][headerName]

# process content
if responseParams.has_key('content'):
self.response.out.write(responseParams['content'])

return

class JsonStringURLEchoHandler(BaseURLEchoHandler):
def parseResponseParams(self, queryString):
requestParams = self.parseRequestQueryStringParams(queryString, ['jsonResponse', 'debugMode'])

responseParams = {}
if requestParams.has_key('jsonResponse'):
responseParams = simplejson.loads(requestParams['jsonResponse'])

isDebugMode = requestParams.has_key('debugMode') and requestParams['debugMode'] == "1"
return responseParams, isDebugMode

class URLGadgetHandler(BaseURLEchoHandler):
UrlGadgetTemplate = '<?xml version="1.0" encoding="UTF-8" ?><Module><ModulePrefs title="URL Gadget generated by UrlEcho service for %s" /> <Content type="url" href="%s"></Content></Module>'

def parseResponseParams(self, queryString):
responseParams = {}
requestParams = self.parseRequestQueryStringParams(queryString, ['destinationUrl', 'debugMode'])
responseParams['content'] = URLGadgetHandler.UrlGadgetTemplate % (requestParams['destinationUrl'], requestParams['destinationUrl'])
isDebugMode = requestParams.has_key('debugMode') and requestParams['debugMode'] == "1"
return responseParams, isDebugMode

class QueryStringHandler(BaseURLEchoHandler):
def parseResponseParams(self, queryString):
responseParams = {}

requestParams = self.parseRequestQueryStringParams(queryString, ['status','headers','content','debugMode'])

# parse status
if requestParams.has_key('status'):
responseParams['status'] = int(requestParams['status'])

# parse headers
if requestParams.has_key('headers'):
headers = requestParams['headers'].split("&")
responseParams['headers'] = dict(map(lambda x: map(urllib.unquote, x.split("=", 1)), headers))

# process content
if requestParams.has_key('content'):
responseParams['content'] = requestParams['content']

isDebugMode = requestParams.has_key('debugMode') and requestParams['debugMode'] == "1"
return responseParams, isDebugMode

class RedirectToGoogleCodeHandler(webapp.RequestHandler):
def get(self):
self.redirect('http://github.com/izuzak/urlecho')

application = webapp.WSGIApplication([('/generateUrlGadget.*', URLGadgetHandler),
('/echoqueryparams.*', QueryStringHandler),
('/echo.*', JsonStringURLEchoHandler),
('/.*', RedirectToGoogleCodeHandler)], debug=True)

def main():
run_wsgi_app(application)

if __name__ == "__main__":
main()

0 comments on commit ffe0984

Please sign in to comment.