Skip to content

Commit

Permalink
Staff version (#47)
Browse files Browse the repository at this point in the history
Adds support for a subset of the staff version of the Darwin API, which provides significantly more detail over the basic LDB API.

The client side access token is the same for both APIs and Huxley will choose the correct Darwin access token to use. If you pass through your token then you will need to use the correct one for the API being called.

There are a bunch of extra fields on the staff API, some have been renamed and some have a different type. For example, the times are all now real DateTime objects, which makes them easier to use, rather than simply display (or do complex parsing). Something to keep in mind with the staff service details is that you don't just get calling points. You get all locations, including passed stations and junctions (for example, Borough Market Jn. and North Kent East Jn. - but you won't get a proper location name for these, just a TIPLOC with whitespace trimmed).

There are also some improvements to the service API. Huxley now returns the service ID in multiple forms (original Base64, percent encoded, GUID and URL safe Base64). The service endpoint will accept any of these and it will also attempt to patch up any malformed IDs. Plain Base64 strings don't always play nicely in URLs (particularly if used in the path, rather than as a query string parameter).
  • Loading branch information
jpsingleton authored Sep 19, 2016
1 parent 47e76cb commit 3bab7f6
Show file tree
Hide file tree
Showing 108 changed files with 20,801 additions and 255 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*.suo
*.user
*.sln.docstates
.vs/

# Build results
[Dd]ebug/
Expand Down
50 changes: 39 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@

## UK National Rail Live Departure Boards JSON proxy

Huxley is a [CORS](http://enable-cors.org/) enabled JSON [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) proxy for the UK National Rail Enquires Live Departure Board [SOAP](http://harmful.cat-v.org/software/xml/soap/simple) [API](http://www.nationalrail.co.uk/46391.aspx) (Darwin).
Huxley is a [CORS](http://enable-cors.org/) enabled JSON proxy for the UK National Rail Enquires Live Departure Board [SOAP](http://harmful.cat-v.org/software/xml/soap/simple) [API](http://www.nationalrail.co.uk/46391.aspx) (Darwin).
It aims to make the API available to many more tools on multiple platforms. You no longer need .NET on Windows to use Darwin.

![Tech arch](https://raw.githubusercontent.com/jpsingleton/Huxley/master/HuxleyTechArch.png)
[![Tech arch](https://raw.githubusercontent.com/jpsingleton/Huxley/master/HuxleyTechArch.png)](https://huxley.unop.uk)

If you want to be informed of updates when they are released then watch the project on GitHub and/or **[follow me on Twitter](https://twitter.com/shutdownscanner)**. You can also read about this and other projects on [my blog](https://unop.uk/).
If you want to be informed of updates when they are released then watch the project on GitHub and **follow [me on Twitter](https://twitter.com/shutdownscanner)**. You can also read about this and other projects on [my blog](https://unop.uk/).
If you are interested in cross-platform .NET then you may enjoy reading [my new book, "ASP.NET Core 1.0 High Performance"](https://unop.uk/book/).

---

[SOAP](http://harmful.cat-v.org/software/xml/soap/simple) is a pain to use (you have to POST specially crafted XML) so this proxy allows you to GET nicely formatted JSON instead ([REST](https://en.wikipedia.org/wiki/Representational_state_transfer)). It also adds [CORS](http://enable-cors.org/) headers so you can access it with JavaScript from a different domain.

Huxley also has a built in CRS code lookup API so you can search for station names from your app. You can also use station names directly in any query. The codes are automatically kept up to date from the official sources.

In addition it has a function for calculating delays which allows you to build useful IoT devices like this [LED strip delay indicator](https://unop.uk/dev/train-disruption-indicator-with-a-blinky-tape-rgb-led-strip-and-raspberry-pi/). You can specify specific trains and it even knows about London Terminals.
In addition it has a function for calculating delays which allows you to build useful IoT devices like this [LED strip delay indicator](https://unop.uk/train-disruption-indicator-with-a-blinky-tape-rgb-led-strip-and-raspberry-pi/). You can specify specific trains and it even knows about London Terminals.

[![Train Disruption Indicator](https://unop.uk/wp-content/uploads/2015/05/trains.jpg "Train Disruption Indicator")](https://unop.uk/dev/train-disruption-indicator-with-a-blinky-tape-rgb-led-strip-and-raspberry-pi/)
[![Train Disruption Indicator](https://unop.uk/wp-content/uploads/2015/05/trains.jpg "Train Disruption Indicator")](https://unop.uk/train-disruption-indicator-with-a-blinky-tape-rgb-led-strip-and-raspberry-pi/)

You can also use it to build mobile web apps such as [InstaBail](https://instabail.uk/), which generates excuses based on real transport disruptions.

Expand All @@ -32,10 +34,10 @@ You can also use it to build mobile web apps such as [InstaBail](https://instaba
There is an example deployment set up [here](https://huxley.apphb.com/).
(**DO NOT USE THIS FOR ANYTHING SERIOUS!**)

Paste this into your web console <kbd>F12</kbd>:
Paste this into your browser developer console <kbd>F12</kbd> (this may not work if the tab is on GitHub due to the Content Security Policy):
```javascript
var r = new XMLHttpRequest();
r.open("GET", "https://huxley.apphb.com/all/stp/from/bxs/1?accessToken=DA1C7740-9DA0-11E4-80E6-A920340000B1", true);
r.open("GET", "https://huxley.apphb.com/all/gtw/from/vic/1?accessToken=DA1C7740-9DA0-11E4-80E6-A920340000B1", true);
r.onreadystatechange = function () {
if (r.readyState != 4 || r.status != 200) return;
var resp = JSON.parse(r.response);
Expand All @@ -57,12 +59,14 @@ There is an additional Python (v2) [example for a Raspberry Pi and Blinky Tape R

## Access Token

You will need to add your access token to the URL. You can register to obtain one [here](https://realtime.nationalrail.co.uk/OpenLDBWSRegistration/Registration).
You will need to add your access token to the URL. You can register to obtain one [here](https://realtime.nationalrail.co.uk/OpenLDBWSRegistration/Registration)
(or [here](http://openldbsv.nationalrail.co.uk/self-signup/register) for the staff version).
Append the `accessToken={Your GUID token}` parameter to the query string for every request.

There is optional support for configuring the access token server side. So you don't need to worry about revealing it.

You can set `DarwinAccessToken` to your NRE access token. If you leave `ClientAccessToken` as an empty GUID then no token is required in the Huxley URL. If you set `ClientAccessToken` to a random GUID and it matches the token in the URL then the `DarwinAccessToken` will be used instead in the SOAP call. Otherwise the URL token is passed straight through. Look in the `Web.config` file for more details.
You can set `DarwinAccessToken` to your NRE access token. If you leave `ClientAccessToken` as an empty GUID then no token is required in the Huxley URL. If you set `ClientAccessToken` to a random GUID and it matches the token in the URL then the `DarwinAccessToken` will be used instead in the SOAP call. Otherwise the URL token is passed straight through. Look in the `Web.config` file for more details.
You can do the same with `DarwinStaffAccessToken` if you are using the staff version.

**N.B.** You should set up these tokens in your deployment platform and not in your source code repository. You'll notice that the values are empty GUIDs by default. The example token used above will only work on the demo server and not directly against the SOAP API.

Expand All @@ -72,7 +76,7 @@ The URL format is `{board}/{CRS|StationName}/{filterType}/{filterCRS|StationName

A station name can be used in place of CRS codes if the name matches only one station (or matches one exactly) but case is not important. See the [CRS section](#crs-station-codes) below for more information.

For all boards you can add an `expand=true` parameter to embed all service details into the board response.
For all boards (except delays) you can add an `expand=true` parameter to embed all service details into the board response. The delays board is expanded by default.

[`/all/{CRS|StationName}?accessToken={token}&expand=true`](https://huxley.apphb.com/all/crs?accessToken=DA1C7740-9DA0-11E4-80E6-A920340000B1&expand=true)

Expand Down Expand Up @@ -109,15 +113,39 @@ Filter stations can be a comma separated list. Filter type and number of rows ar

Filter stations can be a comma separated list. Filter type and number of rows are ignored.

### Staff Departures

[`/staffdepartures/{CRS|StationName}/{filterType}/{filterCRS|StationName}`](https://huxley.apphb.com/staffdepartures/crs?accessToken=DA1C7740-9DA0-11E4-80E6-A920340000B1)

### Staff Arrivals

[`/staffarrivals/{CRS|StationName}/{filterType}/{filterCRS|StationName}`](https://huxley.apphb.com/staffarrivals/crs?accessToken=DA1C7740-9DA0-11E4-80E6-A920340000B1)

### Staff Departures and Arrivals

[`/staffall/{CRS|StationName}/{filterType}/{filterCRS|StationName}`](https://huxley.apphb.com/staffall/crs?accessToken=DA1C7740-9DA0-11E4-80E6-A920340000B1)

### Staff Next

[`/staffnext/{CRS|StationName}/{filterType}/{filterCRSs|StationNames}`](https://huxley.apphb.com/staffnext/crs/to/edb?accessToken=DA1C7740-9DA0-11E4-80E6-A920340000B1)

### Staff Fastest

[`/stafffastest/{CRS|StationName}/{filterType}/{filterCRSs|StationNames}`](https://huxley.apphb.com/stafffastest/crs/to/edb?accessToken=DA1C7740-9DA0-11E4-80E6-A920340000B1)

### Service

[`/service/{Service ID}?accessToken={Your GUID token}`](https://huxley.apphb.com/service/Z/zlpIG8jJacKayAnOXODw==?accessToken=)

The service ID can be found for each service inside the departures and arrivals response.
The service ID can be found for each service inside the departures and arrivals response.
Huxley also returns the ID in URL percent encoded, GUID and [URL safe Base64](https://en.wikipedia.org/wiki/Base64#URL_applications) representations (for non-staff boards).
Likewise, the service endpoint will accept [URL safe Base64](https://tools.ietf.org/html/rfc4648#section-5) service IDs, from various different encoders.

This endpoint also accepts the [GUID representation of the ID](https://huxley.apphb.com/service/8c105350-4235-44f3-b076-87fe829c577e?accessToken=) as `/`, `+` and case sensitivity can cause trouble if you're not careful.
[More information on the wiki](https://github.com/jpsingleton/Huxley/wiki/Train-Service-IDs).

If the ID is a RID (a 15 digit long integer) then the staff API will be used. In this case a staff access token must be used (unless configured server side).

### Delays

The **delays** action performs calculations server side to easily let you know if there are problems on a particular route.
Expand Down
4 changes: 3 additions & 1 deletion src/Huxley/App_Start/NinjectWebCommon.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using Huxley.ldbServiceReference;

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(Huxley.App_Start.NinjectWebCommon), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(Huxley.App_Start.NinjectWebCommon), "Stop")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(Huxley.App_Start.NinjectWebCommon), "Stop")]

namespace Huxley.App_Start {
using System;
using System.Web;
using Microsoft.Web.Infrastructure.DynamicModuleHelper;
using Ninject;
using Ninject.Web.Common;
using ldbStaffServiceReference;

public static class NinjectWebCommon {
private static readonly Bootstrapper Bootstrapper = new Bootstrapper();
Expand Down Expand Up @@ -40,6 +41,7 @@ private static IKernel CreateKernel() {
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
kernel.Bind<ILdbClient>().To<LdbClient>().InRequestScope();
kernel.Bind<LDBServiceSoapClient>().To<LDBServiceSoapClient>().InRequestScope();
kernel.Bind<LDBSVServiceSoapClient>().To<LDBSVServiceSoapClient>().InRequestScope();
return kernel;
} catch {
kernel.Dispose();
Expand Down
4 changes: 2 additions & 2 deletions src/Huxley/Controllers/CrsController.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
Huxley - a JSON proxy for the UK National Rail Live Departure Board SOAP API
Copyright (C) 2015 James Singleton
* http://huxley.unop.uk
Copyright (C) 2016 James Singleton
* https://huxley.unop.uk
* https://github.com/jpsingleton/Huxley
This program is free software: you can redistribute it and/or modify
Expand Down
4 changes: 2 additions & 2 deletions src/Huxley/Controllers/DelaysController.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
Huxley - a JSON proxy for the UK National Rail Live Departure Board SOAP API
Copyright (C) 2015 James Singleton
* http://huxley.unop.uk
Copyright (C) 2016 James Singleton
* https://huxley.unop.uk
* https://github.com/jpsingleton/Huxley
This program is free software: you can redistribute it and/or modify
Expand Down
13 changes: 11 additions & 2 deletions src/Huxley/Controllers/LdbController.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
Huxley - a JSON proxy for the UK National Rail Live Departure Board SOAP API
Copyright (C) 2015 James Singleton
* http://huxley.unop.uk
Copyright (C) 2016 James Singleton
* https://huxley.unop.uk
* https://github.com/jpsingleton/Huxley
This program is free software: you can redistribute it and/or modify
Expand Down Expand Up @@ -42,6 +42,15 @@ protected static AccessToken MakeAccessToken(Guid accessToken) {
return new AccessToken { TokenValue = accessToken.ToString() };
}

protected static ldbStaffServiceReference.AccessToken MakeStaffAccessToken(Guid accessToken)
{
if (HuxleyApi.Settings.ClientAccessToken == accessToken)
{
accessToken = HuxleyApi.Settings.DarwinStaffAccessToken;
}
return new ldbStaffServiceReference.AccessToken { TokenValue = accessToken.ToString() };
}

protected static string MakeCrsCode(string query) {
// Process CRS codes if query is present
if (!string.IsNullOrWhiteSpace(query) &&
Expand Down
96 changes: 87 additions & 9 deletions src/Huxley/Controllers/ServiceController.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
Huxley - a JSON proxy for the UK National Rail Live Departure Board SOAP API
Copyright (C) 2015 James Singleton
* http://huxley.unop.uk
Copyright (C) 2016 James Singleton
* https://huxley.unop.uk
* https://github.com/jpsingleton/Huxley
This program is free software: you can redistribute it and/or modify
Expand All @@ -22,22 +22,100 @@ You should have received a copy of the GNU Affero General Public License
using System.Threading.Tasks;
using System.Web.Http;
using Huxley.Models;
using Huxley.ldbServiceReference;
using System.Web;

namespace Huxley.Controllers {
public class ServiceController : LdbController {
namespace Huxley.Controllers
{
public class ServiceController : LdbController
{

public ServiceController(ILdbClient client)
: base(client) {
: base(client)
{
}

// GET /service/ID?accessToken=[your token]
public async Task<ServiceDetails> Get([FromUri] ServiceRequest request) {
public async Task<object> Get([FromUri] ServiceRequest request)
{
var token = MakeAccessToken(request.AccessToken);

// The Darwin API requires service ID to be a standard base 64 string
// As it's simply a GUID there will always be maximum padding (==)
// By this point it doesn't matter if it was percent-encoded
// It will have been decoded and we get the raw characters
if (request.ServiceId.Length == 24)
{
try
{
if (Convert.FromBase64String(request.ServiceId).Length == 16)
{
var s = await Client.GetServiceDetailsAsync(token, request.ServiceId);
return s.GetServiceDetailsResult;
}
}
catch
{
// Not a base 64 encoded GUID or the API rejected it
}
}

// If ID looks like a RID (15 decimal digit long base 10 integer) then use the staff API
long rid;
if (long.TryParse(request.ServiceId, out rid))
{
var staffToken = MakeStaffAccessToken(request.AccessToken);
var staffService = await Client.GetStaffServiceDetailsAsync(staffToken, request.ServiceId);
return staffService.GetServiceDetailsResult;
}

// We also accept the standard hexadecimal (base 16) GUID representation
Guid sid;
if (Guid.TryParse(request.ServiceId, out sid)) {
if (Guid.TryParse(request.ServiceId, out sid))
{
request.ServiceId = Convert.ToBase64String(sid.ToByteArray());
}
var token = MakeAccessToken(request.AccessToken);

// We support URL safe base 64 encoding as it's more suitable for this situation
// https://en.wikipedia.org/wiki/Base64#URL_applications
// https://tools.ietf.org/html/rfc4648#section-5
// This decoder uses a number suffix for padding so the URLs will also be shorter
// Other encoders are available as part of ASP.NET Core
// - Microsoft.AspNet.WebUtilities.WebEncoders in RC1
// - Microsoft.Extensions.WebEncoders for RC2 / 1.0
// For more info read ASP.NET Core 1.0 High Performance (https://unop.uk/book) :)
var id = request.ServiceId;
if (id.Length == 22)
{
// IDs always have 2 characters of padding
id += "2";
}
if (id.Length == 23)
{
try
{
var sidBytes = HttpServerUtility.UrlTokenDecode(id);
if (sidBytes != null && sidBytes.Length == 16)
{
request.ServiceId = Convert.ToBase64String(sidBytes);
}
}
catch
{
// Not Base64 URL encoded
}
}

// If ID wasn't percent-encoded then it may be missing / + =
// We try to fix it up if it isn't the correct length
while (!request.ServiceId.EndsWith("=="))
{
request.ServiceId += "=";
}
while (request.ServiceId.Length < 24)
{
request.ServiceId = "/" + request.ServiceId;
}

var service = await Client.GetServiceDetailsAsync(token, request.ServiceId);
return service.GetServiceDetailsResult;
}
Expand Down
Loading

0 comments on commit 3bab7f6

Please sign in to comment.