Gebruik van content.omroep.nl

Dit document beschrijft het gebruik van http://content.omroep.nl

Het contentplatform is meer dan 1 simpele webserver; het is een samenspel van webservers en scripts die ervoor moeten zorgen dat er via content minimaal tussen 20-30 Gbit per seconde aan dataverkeer naar buiten geduwd kan worden. Er wordt uitgelegd waar rekening mee te houden bij het gebruik van http://content.omroep.nl en hoe de GeoIP en anti-hotlink features gebruikt kunnen worden.

content.omroep.nl is een webserveromgeving die bedoeld is voor het uitserveren van grote bestanden. In het bijzonder flash video's, H264-bestanden en podcasts. Vanwege de toegenomen populariteit van met name flash video is content geschaald om een bandbreedte tussen de 50 en 100 Gbit te kunnen vullen.

Om dit mogelijk te maken is het mechanisme achter het contentplatform iets aangepast, dit staat uitgelegd in de sectie Redirector

Tegelijkertijd is de functionaliteit van het contentplatform uitgebreid. Het is nu ook mogelijk om content af te schermen op basis van GeoIp of om te beschermen tegen hotlinks Daarnaast is het mogelijk om voor flash video (zowel flv als h264) en mp3 files pseudo streaming te doen. Ook kan er gebruik gemaakt worden van een extra content-type tag om bijvoorbeeld geforceerde downloads te faciliteren.

Bij een “normale” webserver is het meestal zo dat als er een bestand opvraagd wordt, de webserver een HTTP status code 200 teruggeeft en het bestand direct zelf uitserveert. Bij content.omroep.nl is dat niet meer zo, in verband met de schaalgrootte van de hele omgeving. Als er nu bij het contentplatform een bestand opgevraagd wordt, dan kan het voorkomen dat content niet zelf het bestand uitserveert, maar een redirect stuurt (“HTTP 307 Temporary Redirect” status code) naar een uitspeelserver die de uiteindelijke content uit kan serveren.

Dit is een standaard HTTP response, die door alle browsers ondersteund wordt. Vervolgens zal de browser het bestand ophalen bij de webserver waar naar geredirect wordt.

Deze verwijzing is nodig om het verkeer uit te kunnen splitsen naar populariteit. Populaire bestanden kunnen op deze manier vanaf snelle webservers uitgeserveerd worden, terwijl de minder populaire bestanden vanuit het archief uitgeserveerd kunnen worden.

Niet deeplinken aan de redirect urls

De redirect urls die gegenereerd worden zijn alleen voor de aanvrager (ip adres) bruikbaar. Stel dat er een request plaatsvond op http://content.omroep.nl/test/wilhelmus.mp3 dan wordt er een tijdelijke redirect gegenereerd worden die er ongeveer zo uit ziet: http://content1c.omroep.nl/urishieldv2/l27m4b485f123b8d2a0f00526e3ea1000000.1d2a05c90addf69c1b2968a21dc4e002/test/wilhelmus.mp3 Vanaf deze locatie kan het bestand gedownload worden, maar deze link is niet deelbaar met anderen. Dit is gedaan om ervoor te zorgen dat de uitsplitsing van verkeer naar populariteit goed blijft werken. Stel dat op enig moment /test/wilhelmus.mp3 niet populair is, dan wordt er naar een archief server verwezen. Als nou deze verwijzing ergens permanent opgeslagen zou worden en /test/wilhelmus.mp3 wordt opeens heel populair, dan kan die archief server de grote load niet aan. Het is dus zeer wenselijk dat volgende requests op dit bestand via een snelle webserver uitgeserveerd worden. Oftewel, die redirect link moet vooral niet permanent op een site komen, in plaats daarvan moet altijd de oorspronkelijke url: http://content.omroep.nl/test/wilhelmus.mp3 gebruikt worden, zodat van moment tot moment bijgehouden kan worden wat populair is en dus vanaf snelle webservers uitgeserveerd moet worden.

IP afscherming op redirect urls

In de redirect URLs zit een afscherming op IP basis. Dat betekent dat het IP adres van het device dat de originele aanvraag doet (content.omroep.nl/test/wilhelmus.mp3) en de uiteindelijke aanvraag (contentXX.omroep.nl/urishield/<hash>/test/wilhelmus.mp3) gelijk moeten zijn.

Voorheen was er een andere afscherming, op basis van tijdelijk houdbare urls. Daar zat geen IP adres in, maar inplaats daarvan een timestamp. Door de komst van mobiele devices die voornamelijk op basis van range requests werken was de afscherming op basis van tijdcode niet meer werkbaar (want voor een clip van 1 uur lengte blijft een device een uur lang range requests doen en tegen die tijd is de timeout al lang verlopen). Een lange timeout van -zeg- een uur is niet werkbaar omdat die al zo lang is dat er dan deeplinks de wereld ingeslingerd kunnen worden die voor problemen met de caching kunnen gaan zorgen. en een korte timeout (van een minuut ofzo) in combinatie met een terugredirect naar de redirector om zo een nieuwe redirect url te genereren blijkt in de praktijk ook niet te werken omdat er dan veel devices blijken te zijn die elk range request (dwz elke paar seconde) via de redirector opvragen, wat weer voor teveel load op de redirector en de daarachterhangende machinerie zorgt.

Aanpassen crossdomain.xml

Flash (swf) bestanden gebruiken een bestand genaamd crossdomain.xml om te zien in hoeverre redirects gehonoreerd mogen worden. Wij adviseren om in crossdomain.xml de volgende entry op te nemen:

<allow-access-from domain="content*.omroep.nl" />

Op deze manier worden alle servers waar content.omroep.nl naartoe kan redirecten gecovered.

Het is mogelijk om op bepaalde delen van het contentplatform GeoIP afscherming in te laten stellen.

Hiertoe kan een verzoek bij de NPO ICT Servicedesk ingediend worden. In het verzoek moet aangegeven worden:

  • Welk deel van GeoIP afscherming voorzien moet worden. B.v.

/xrtv/clips/afgeschermd

  • Voor welke regio de GeoIP afscherming geldt. Dit kan uitsluitend op

landen niveau. B.v. Nederland of Nederland+Antillen.

  • Een redirect url waar naar verwezen kan worden indien men niet

door de geoip check komt. Bijvoorbeeld: http://www.xrtv.nl/geo-sorry-page.html

Nadat dit ingesteld is zullen alle requests op content onder http://content.omroep.nl/xrtv/clips/afgeschermd tegen een GeoIP check aangehouden worden. Slaagt de check dan wordt men geredirect naar de eigenlijke content. Faalt de test dan gaat de redirect naar http://www.xrtv.nl/geo-sorry-page.html

Uitzonderingen

Niet alle extensies worden door door GeoIP afscherming beschermd. Er zijn een aantal uitzonderingen welke direct door de proxy behandeld worden. Het kan dus zijn dat bij het testen of de GeoIP afscherming goed zijn werk doet, er een resultaat terugkomt wat misschien onverwacht is. De volgende extensies worden niet afgeschermd door GeoIP:

  • xml
  • html
  • swf
  • ico
  • txt
  • m3u8
  • jpg
  • gif
  • png
  • js

Het is mogelijk om op bepaalde delen hotlink afscherming in te laten stellen. Met hotlink afscherming kan ervoor gezorgd worden dat er niet meer direct vanaf een externe site naar bepaalde content op het contentplatform gelinked kan worden. Links naar deze content worden alleen gehonoreerd indien er een juiste code meegegeven wordt die berekend is op basis van een geheim password, een timestamp en het pad naar de content. Op deze manier kan ervoor gezorgd worden dat b.v. alleen vanuit uw eigen website gelinkt kan worden naar deze content, om er zeker van te zijn dat de content in de gewenste context getoond wordt.

Het mechanisme werkt op basis van zgn tokens. Een token is een md5 hash van de concatenatie van:

  • een geheime string
  • het pad van de te contenten content
  • een timestamp in hex

Gewapend met een token en een timestamp in hex kan er op twee manieren aan de content gelinked worden:

  • Als HTTP GET parameters md5 en t:
http://content.omroep.nl/pad/naar/clip.flv?md5=$token&t=$t_hex
  • Als clean url, beginnend met de string “/secure” en vervolgens de

md5 en t parameters in de url verwerkt:

http://content.omroep.nl/secure/$token/$t_hex/pad/naar/clip.flv

Het is gebleken dat de tweede variant (met /secure) het beter doet in flash players, deze vallen namelijk over de meegegeven HTTP get parameters van de eerste variant.

Hieronder is een stukje voorbeeld code in php voor het genereren van de urls. Voorbeelden in andere programmeertalen staan op de website van lighttpd

<?php
$secret = "heelgeheimpassword";
$uri = "/xrtv/clips/afgeschermd/file.flv";
$server = "content.omroep.nl";
 
$t_hex = sprintf("%08x", time());
$token = md5($secret.$uri.$t_hex);
$url1 = sprintf("http://%s%s?md5=%s&t=%s", $server,$uri,$token,$t_hex);
$url2 = sprintf("http://%s/secure/%s/%s%s", $server,$token,$t_hex,$uri);
 
printf('<a href="%s">Link style 1</a>', $url);
printf('<a href="%s">Link style 2</a>', $url);
?>

Bij de NPO ICT Servicedesk kan een verzoek ingediend worden om bepaalde delen van het contentplatform van hotlink bescherming te voorzien. In het verzoek moet aangegeven worden:

  • Welk deel van hotlink bescherming voorzien moet worden. Bijvoorbeeld

/xrtv/clips/incontextonly

  • Het secret dat gebruikt wordt voor het genereren va het token

(dit secret moet tegelijkertijd aan beide kanten bekend zijn en kan dus niet zomaar aan een van beide kanten gewijzigd worden!)

  • Een timeout die aangeeft hoe lang de tijdelijke urls geldig

mogen blijven

  • Een optionele pagina waarheen verwezen kan worden als iemand een ongeldige

pagina bezoekt

Nadat dit is ingesteld zullen links voor content onder http://content.omroep.nl/xrtv/clips/incontextonly alleen gehonoreerd worden indien de correcte md5 hash en timestamp meegegeven worden. Requests met verkeerde waardes voor de md5 hash of requests op links waarvan de timeout verstreken is krijgen een “HTTP 403 Forbidden” statuscode terug.

Meerdere shared secrets op dezelfde uri

Het kan voorkomen dat meerdere applicaties bij dezelfde content moeten kunnen, maar dat het niet gewenst is om die applicaties onderling hetzelfde secret te laten delen.

In dit geval kan er een extra parameter genaamd appname meegegeven worden. Aan elke appname kan een eigen shared secret gekoppeld worden. Stel de configuratie ziet er zo uit:

uri appname shared secret
/xrtv/clips/incontextonly NULL “default_secret”
/xrtv/clips/incontextonly npoplayer “npo_secret”
/xrtv/clips/incontextonly xrtvplayer “xrtv_secret”

Afhankelijk van hoe de clip aangeroepen wordt, kunnen nu verschillende shared secrets gebruikt worden:

url shared secret
http://content.omroep.nl/secure/$token/$t_hex/xrtv/clips/incontextonly/file.flv “default_secret”
http://content.omroep.nl/secure/$token/$t_hex/xrtv/clips/incontextonly/file.flv?appname=randomzooi “default_secret”
http://content.omroep.nl/secure/$token/$t_hex/xrtv/clips/incontextonly/file.flv?appname=npoplayer “npo_secret”
http://content.omroep.nl/secure/$token/$t_hex/xrtv/clips/incontextonly/file.flv?appname=xrtvplayer “xrtv_secret”

Wat niet kan is voor de ene app wel en voor de andere geen afscherming te doen. Dwz zo gauw je ergens afscherming op zet is het voor alles afgeschermd. Hooguit heb je keuze met wel secret je toegang gaat krijgen.

In sectie Hotlink bescherming wordt uitgelegd hoe op bepaalde delen van de content ervoor gezorgd kan worden dat er niet meer vanaf externe (dwz ongeauthoriseerde) sites naar gelinkt kan worden. Deze stijl van hotlink bescherming heeft ook een tijdscode in zich en hier zou het dus ook kunnen gebeuren dat een browser te lang op een URL zit, waardoor deze niet meer geldig is op het moment dat ie opgevraagd wordt.

Een oplossing in dit geval is een challenge-response protocol.

De gedachte hier is dat we in de redirector state bij gaan houden en op basis van die state kunnen besluiten om na een redirect wegens timeout een nieuwe redirect te maken. De hotlink bescherming gebeurt hier dan niet door het maken van een /secure url, maar door een challenge/response conversatie. Inplaats van 1 request zijn er nu 2 requests; eerst een request om een challenge op te vragen, en daarna pas het “echte” request op de eigenlijke data, waar in het eigenlijke request (“geef mij clip x”) ook een response op de challenge zit.

De eerste stap is dat er vantevoren wordt ingesteld dat er op een bepaald gedeelte van de content challenge-response authenticatie plaats moet vinden. Hiertoe kan een verzoek bij de NPO ICT Servicedesk ingediend worden. In het verzoek moet aangegeven worden:

  1. De prefix van de content die beschermd moet worden (b.v. /xrtv/clips/afgeschermd)
  2. Hoeveel maal een afgeschermde URL opgevraagd mag worden na een succesvolle challenge-response dialoog (default 1)
  3. Of er een IP check plaats moet vinden die kijkt of de aanvrager van de challenge hetzelfde IP adres heeft als de aanvrager van de content (default true)
  4. een geheime string die wordt gebruikt om een response te kunnen maken op basis van een gegeven challenge
  5. een redirect url waarnaartoe verwezen moet worden indien gepoogd wordt de content op te vragen zonder dat daar een succesvole challenge-response dialoog aan vooraf is gegaan

Nadat bovenstaande zaken zijn ingesteld werkt het als volgt:

De challenge is op te vragen door een extra parameter “getchallenge” mee te geven. Bijvoorbeeld:

http://content.omroep.nl/xrtv/clips/afgeschermd/file.flv?getchallenge

Deze levert een id en een challenge, welke zowel in de headers als in een stukje xml terugkomen. De headers zijn

X-Id: [het id]
X-Challenge: [de challenge]

De xml ziet er zo uit

<xml>
<id>[het id]</id>
<challenge>[de challenge]</challenge>
</xml>

Vervolgens moet uw applicatie de response uitrekenen en samen met het id aan het contentplatform geven. De response is de md5 hash van de geheime string, de opgevraagde clip en de challenge. In php zou het er zo uit zien

$secret = "heelgeheimpassword";
$uri = "/xrtv/clips/afgeschermd/file.flv";
$challenge = "2a9f101b"; # de challenge zoals die uit ?getchallenge kwam

$response = md5($secret . $uri . $challenge);

Het doorgeven van het id en de response gaat met de id en response parameters. Als in:

http://content.omroep.nl/xrtv/clips/afgeschermd/file.flv?id=$id&response=$response

Dit request wordt door de contentserver gevalideerd en indien de validatie slaagt volgt er een redirect naar de eigenlijke content.

Onze webserverfarm ondersteunt een vorm van pseudo streaming voor flash video bestanden die het mogelijk maakt om te skippen in video, nog voordat deze helemaal gecontent is. Gebruik hiervan staat hier uitgelegd.

Voor h264-bestanden maken we gebruik van de mod_h264_streaming module (versie 2) waarmee op basis van tijdcodes geskipped kan worden in h264 encoded mp4 bestanden. Momenteel kan deze worden module losgelaten op '.m4v' en '.mp4' bestanden. Let op: deze module past de inhoud van de h264 bestanden aan; de headers worden naar het begin v/d file gemoved zodat browsers meteen aan het begin v/h inladen v/e bestand informatie over tijdcodes tot hun beschikking hebben en er meteen in bestanden gesprongen kan worden. Indien het bestand as-is uitgeserveerd moet worden kan dat door de volgende parameter aan de url toe te voegen:

  • pure=true

De regeltjes voor het al-dan-niet gebruiken van deze module zijn:

  • normaliter wordt deze module niet gebruikt, dus als je een willekeurig mp4 of m4v bestand aanroept zonder verdere parameters dan wordt de module niet gebruikt en wordt het bestand as-is uitgevoerd.
  • pas als er een start= of end= parameter wordt meegegeven dan wordt de module geactiveerd
  • uitzondering hierop is een enkele start=0. dan wordt de module niet geactiveerd
  • bij een combinatie start=0&start=N wordt de module wel geactiveerd (en “wint” de laatste start=N)

Daarnaast is het mogelijk om in mp3's te skippen door een byte-offset mee te geven met de “start=” parameter en een einde aantegeven met de “end=” parameter. Er kan dus aan content gelinked worden middels

http://content.omroep.nl/xrtv/file.mp3?start=$offset

waarbij de content de eerste $offset bytes zal skippen.

Om de byterange van $offset1 tot $offset2 uit te spelen kan er gelinked worden middels

http://content.omroep.nl/xrtv/file.mp3?start=$offset1&end=$offset2

Default wordt het begin van de file als start en het eind van de file als end waarde genomen. Indien er een range opgegeven wordt die niet gehonoreerd kan worden (b.v. start > end of end > filesize) dan wordt er een HTTP Error 416 Requested Range not satisfiable gegenereerd.

Het Content-Type dat de webserver teruggeeft hangt af van het filetype dat wordt opgevraagd. Bijvoorbeeld, alls .mp4 bestanden krijgen “video/mp4” als Content-Type. Dat mechanisme zorgt ervoor dat je browser de geschikte actie uit kan voeren, bv een player opstarten die mp4 bestanden kan afspelen.

Soms is dit echter niet gewenst. Dan wil je dat het bestand gewoon opgeslagen wordt ipv afgespeeld. Dit kan afgedwongen worden door een custom Content-Type “application/octet-stream” mee te laten geven.

Response headers kunnen gezet worden door in het request toe te voegen:

sethdr=<Header>:<Value>

of voor het zetten van meerdere headers:

      sethdr1=<Header1>:<Value1>
      sethdr2=<Header2>:<Value2>
      sethdr3=<Header3>:<Value3>
      ...

Bijvoorbeeld, onderstaande URL:

http://content.omroep.nl/test/wilhelmus.mp3?sethdr=Content-Type:application/octet-stream

leidt ertoe dat in de response de volgende header voorkomt:

Content-Type: application/octet-stream

Speciale karakters in headers kunnen via url encoding doorgegeven worden, het simpelst door een + voor een spatie te gebruiken. B.v.

    sethdr=X-Test:test+met+spaties

Uit veiligheidsoverwegingen kunnen geen willekeurige response headers gezet worden op deze manier. Uitsluitend de volgende headers kunnen gezet worden:

  • X-*
  • Content-Type
  • Content-Description
  • Content-Disposition
  • Content-Transfer-Encoding

Deze optie is beschikbaar voor alle type bestanden m.u.v. de volgende types:

  • *.xml, *.html, *.swf, *.ico, *.txt, *.m3u8, *.jpg, *.gif, *.png, *.js: deze worden door de redirector zelf afgehandeld en deze heeft niet de mogelijkheid return headers in te stellen.

Voor de volgende types gelden speciale regels:

  • *.mp4, *.m4v

Deze bestanden worden normaliter door de pseudo streaming module afgehandeld. Deze pikt het request af voordat eventuele response headers gezet kunnen worden. De workaround is om &pure=true toe te voegen aan de query string. Op die manier wordt het bestand “as-is” uitgeserveerd en worden de response headers wel meegenomen. Voorbeeld:

FIXME: onderstaande sectie is niet meer van toepassing, omdat er tegenwoordig geen timestamps meer zitten in de redirect links.

De iPhone media player gaat niet goed samen met de huidige opzet van het contentplatform. Dat komt omdat deze zicht niet houdt aan het niet hergebruiken van redirect urls adagium. Zie http://mobiforge.com/developing/story/content-delivery-mobile-devices

Apple iPhone uses HTTP byte-ranges for requesting audio and video files.
First, the Safari Web Browser requests the content, and if it's an audio or
video file it opens it's media player. The media player then requests the
first 2 bytes of the content, to ensure that the Webserver supports byte-range
requests. Then, if it supports them, the iPhone's media player requests the
rest of the content by byte-ranges and plays it.

Als de MPMoviePlayerController class gebruikt wordt, en het object wordt geinitialiseerd met initWithContentURL dan doet Safari het eerste request. Die krijgt van de redirector de deeplink naar de uitspeelserver terug. Die deeplink wordt aan de media player gegeven en die wordt steeds hergebruikt.

Dit resulteert erin dat de iphone player dezelfde deeplink (met steeds andere range headers) op blijft vragen en dus na korte tijd tegen de timeout van de uitspeelserver opbotst.

Wat valt hier nou tegen te doen?

Terugredirecten bij timeout

In de uitspeelservers waarnaartoe geredirect wordt zit al support om netjes om te gaan met oude links. Als er namelijk request binnenkomt op een link waarvan de tijdsduur vertreken is, dan wordt er een redirect (HTTP 301) terug naar de redirector gegenereerd. Op zijn beurt zal de redirector weer een verse nieuwe deeplink naar een uitspeelserver genereren die vervolgens gebruikt kan worden.

De situatie is dus dat de player al een aantal deelrequests heeft gedaan, steeds HTTP 206 (partial content) terugkrijgt en na het zoveelste request “opeens” een HTTP 301 terugkrijgt. Uit testen blijkt dat de iphone player dit snapt. Derhalve is er bij onbeschermde content geen probleem voor de iPhone player. Maar content waar hotlink afscherming op van toepassing is heeft wel een probleem in dit scenario.

Het terugredirecten bij een timeout kan dus werken voor onbeschermde content, maar heeft een probleem bij content waar hotlink protectie op van toepassing is. Er wordt namelijk teruggeredirect naar een onbeschermde url, die dus niet gehonoreerd zal worden voor de delen van het contentplatform waar hotlink protectie op van toepassing is. En op die manier zal men toch eindigen met een HTTP 401 Forbidden code.

Dit probleem doet zich alleen maar voor bij content die gelinkt wordt als

http://content.omroep.nl/secure/$token/$t_hex/pad/naar/clip.flv

Indien de alternatieve manier van linken gebruikt wordt:

http://content.omroep.nl/pad/naar/clip.flv?md5=$token&t=$t_hex

dan worden de parameters doorgestuurd naar de uitspeelservers en zullen bij een timeout op de uitspeelserver ook weer terugkomen.

Zoals uitgelegd in sectie Timeouts op redirect urls is dit een bug, welke op de nominatie staat om gefixed te worden.

So far, so good. Maar, in die hotlink urls zit ook een timecode en is ook een timeout op van toepassing.

In geval van de iPhone player betekent dat dat content voor deze player een langere hotlink timeout nodig gaat hebben. Want, wat gebeurt er:

  1. er wordt door de applicatie een link (compleet met $token en $t_hex) gegenereerd
  2. deze gaat naar het contentplatform
  3. het contentplatform redirect naar een uitspeelserver
  4. de iPhone player gaat stukje bij beetje (range requests) de data opvragen
  5. op enig moment loopt deze aan te gen de timeout van de uitspeelserver
  6. die redirect terug naar content.omroep.nl
  7. op het contentplatform vind de hotlink check weer plaats
  8. als de hotlink timeout nog niet verstreken is dan wordt er weer een redirect naar een uitspeelserver gegenereerd
  9. lather, rinse, repeat
  10. op enig moment is de hotlink timeout wel verstreken
  11. en dan krijg je van de redirector een redirect naar een “sorry de timeout is verstreken” pagina.

In tegenstelling tot de uitspeelserver timeout is de hotlink timeout per applicatie in te stellen. Op zich volstaat het dus om de hotlink timeout flink op te rekken, als deze langer is dan de lengte van het langste clipje, dan is er weinig aan de hand.

Vandaar dat wij ook een anti hotlink mechanisme bieden dat niet gebaseerd is op timeouts, zie Hotlink bescherming zonder timeouts

Voor vragen over het contentplatform kunt u zich wenden tot de NPO ICT Servicedesk, welke te bereiken is via servicedesk@omroep.nl telefoon 035 67 73 555.