I ett nyligen projekt fick vi i uppdrag att bygga den mobila versionen av en befintlig webbplats. Den befintliga webbplatsen var egentligen inte ett bra kandidat för ett responsivt design, så vi bestämde oss för att skapa en helt ny version av webbplatsen för mobila enheter.

_(foto av Johan Larsson)
Till en början tänkte vi på att använda mönstret m.example.com, där webbplatserna är helt separata domäner. Dock eftersom vi inte hanterar infrastrukturen för den här webbplatsen ville vi gå med en lösning som skulle kräva så få infrastrukturändringar som möjligt. Att stödja en annan domän på samma servrar är inte särskilt svårt, men det är bara en uppgift som är lätt att råka fel på och inte nödvändig i vårt fall.
Jag har även släppt denna teknik för alla att enkelt använda i en rails-app via en rails-motor:
- https://github.com/milesmatthias/cdddn
- http://rubygems.org/gems/cdddn
Intro till CloudFront
En stor fördel med den här specifika webbplatsen är att den använder CloudFront. CloudFront är ett innehållsleveransnätverk (CDN), där statiska webbsidor är värd på 52 datacenter runt om i världen. Detta gör att sidan kan serveras till webbläsaren så snabbt som möjligt eftersom oavsett var i världen du befinner dig, så kommer du inte vara långt ifrån en av CloudFronts noder så att det tar mindre tid att leverera webbplatsen till din enhet. CloudFront är en del av Amazons AWS-erbjudande och förutom att garantera hastighet, garanterar de även drifttid. (Till skillnad från S3, där endast drifttid garanteras.)
När du använder CloudFront ber du den om var din webbserver finns så att CloudFront kan få den senaste versionen av webbsidorna den serverar åt dig. CloudFront frågar även din webbserver efter nya versioner av dina webbsidor varje gång, eller vilken tidsintervall du konfigurerar i ditt CloudFront-kontrollpanel. Normalt sett är det någonstans runt 24 timmar. Detta gör att CloudFront kan fokusera på tillgänglighet och hastighet, samtidigt som belastningen på din webbserver minskas mycket. (Det tar även tid för CloudFront att ta en ny version av en sida och uppdatera alla 52 datacentern.)
Dock introducerar detta problemet med att spåra status. Till exempel, om din webbplats serveras helt av CloudFront, hur ska du stödja användare som loggar in på din webbplats? Om jag är en användare som heter Bob och sidan på /my/account
säger "Hej Bob!", kommer CloudFront att tro att alla som begär sidan på /my/account
ska se versionen av sidan som säger "Hej Bob!". Användare (förutom kanske personer som heter Bob…) kommer att bli mycket förvirrade. Inte bara det, utan vad om en användare uppdaterar något värde i sitt konto och förväntar sig att ändringen ska visas omedelbart? CloudFront får inte en ny version av sidan på varje begäran (det skulle i princip göra meningen med CloudFront betydelselös), det väntar varje gång i några minuter för att få en ny version. CloudFront har adresserat flera av dessa frågor med nya funktioner, men du måste vara medveten om dessa implikationer när du använder CloudFront.
I vårt fall har webbplatsen vi jobbar med inte konceptet "användare" och ingen kan logga in på systemet. Dock vill vi fortfarande att CloudFront ska visa olika versioner av webbplatsen beroende på vilken enhet du begär webbplatsen ifrån (mobil eller dator).
CloudFront kan göra enhetsdetektion åt dig och sedan skicka en speciell huvudtext till din webbserver för att säga "Ge mig versionen av sidan för en telefon", dock ville vi ha kontroll över metoden för enhetsdetektion (besvara frågan - är detta en telefon eller en dator?) och vi ville ha möjligheten för slutanvändare och utvecklare att åsidosätta den enhet som valts för dem. Till exempel kan slutanvändare på en surfplatta bara vilja se datorversionen även om vi visar dem mobilversionen till en början. Våra utvecklare kommer även att hitta det besvärligt att byta enhet varje gång de vill byta mellan versionerna när de bygger webbplatsen.
Vår lösning
Så här är metoden vi kom på för att ha både en dator- och mobilversion av en webbplats på samma domän och serveras av CloudFront. Ett nödvändigt påpekande är att CloudFront normalt inaktiverar alla cookies för din domän, men du kan konfigurera den att tillåta cookies som du namnger. I vårt fall sa vi till CloudFront att tillåta en cookie som heter 'enhet'. Observera även att vi är i kontexten av en rails 4-app, men den här arkitekturen kan replikeras i vilken stack som helst. Huvudpoängen här är att klienten måste lista ut vilken enhet användaren är på, tillåta dem att åsidosätta det, och hålla servern informerad om enhetstypen så att servern kan servera de rätta sidorna (detta görs via cookies).
På varje sida måste vi göra något likt detta (kodsnutt från app/views/layout/application.html.erb)
<%= javascript_include_tag "mobile_detection" %>
<script>
if (!window.location.href.match(/get_device|set_device/)){
console.log('loggar window.location.href = ' + window.location.href);
localStorage.setItem('referrer_href', window.location.href);
}
var device_cookie = cookie.get('device'),
cookiesEnabled = cookie.enabled();
if (cookiesEnabled && device_cookie == undefined) {
window.location = '/get_device.html'
}
</script>
Ovanstående kod kommer enbart ihåg vilken sida du landade på (vanligtvis startsidan, men vi vill att klienten ska kunna dela länkar och få det att fungera på mobilen också), och sedan omdirigerar dig till /get_device
-sidan för att detektera din enhet om vi inte gjort det ännu. Observera användandet av localStorage
, vilket innebär att detta endast kommer att fungera på IE8+ och inte kommer att fungera på Opera Mini.
Observera även att ovanstående kodsnutt placeras i <head>
i layouten så att den kommer att köras så snabbt som möjligt och före några skript som datorversionen kan ha. I det här fallet använder datorversionen mycket javascript för att ladda mycket innehåll, så vi ville egentligen inte att allt det skulle hända på en telefon innan webbplatsen insåg att användaren skulle se mobilversionen av webbplatsen.
På varje mobil sida måste vi göra detta (från `app/views/layout/application.mobile.erb):
<script>
var device_cookie = cookie.get('device'),
cookiesEnabled = cookie.enabled();
if (cookiesEnabled && device_cookie == 'desktop') {
window.location.reload(true);
}
</script>
Detta eftersom mobila enheter cachar sidorna kraftigt (vilket är förståeligt) så om användaren har ändrat enheten de är på, måste vi se till att vi skickar den uppdaterade cookien till servern även om URL:en inte har ändrats (och därmed cachen tror att det är samma sida som ska serveras). true
i reload
-funktionsanropet ber webbläsaren att ignorera vad som finns i cachen.
Observera att vi gör exakt samma sak på varje dator-sida också, förutom att kontrollera om användaren har ändrat sin enhet till 'mobile'
.
Enhetsdetektion på /get_device
(app/views/home/get_device.html)
<script>
function isMobile(){
var MOBILE_USER_AGENTS = 'palm|blackberry|nokia|phone|midp|mobi|symbian|chtml|ericsson|minimo|' +
'audiovox|motorola|samsung|telit|upg1|windows ce|ucweb|astel|plucker|' +
'x320|x240|j2me|sgh|portable|sprint|docomo|kddi|softbank|android|mmp|' +
'pdxgw|netfront|xiino|vodafone|portalmmm|sagem|mot-|sie-|ipod|up\\.b|' +
'webos|amoi|novarra|cdm|alcatel|pocket|ipad|iphone|mobileexplorer|' +
'mobile|zune',
mobile_regex = new RegExp(MOBILE_USER_AGENTS, "i");
return navigator.userAgent.match(mobile_regex);
}
// ställer in cookien för enhet till vad vi detekterar
var detected_device = isMobile() ? 'mobile' : 'desktop';
cookie.set('device', detected_device, {
domain: '<%= Rails.env.development? ? '' : Settings.host %>',
path : '/'
});
// window.location till varifrån du kom ifrån
window.location.assign(localStorage.getItem('referrer_href'));
</script>
Enhet åsidosättning på /set_device
(app/views/home/set_device.html)
<body style="background-color:#f1f1f2">
<h1>Vilken enhet vill du se webbplatsen som?</h1>
<form id="device_selection_form">
<input type="radio" name="device_option" value="desktop">Dator</input>
<input type="radio" name="device_option" value="mobile">Mobil</input>
<button id="set_device_btn">Ställ in enhet</button>
</form>
<script>
document.getElementById('set_device_btn').addEventListener('click', function(e){
// ställer in cookien för enhet till ditt val
var options = document.getElementsByName('device_option'),
selected_device = '';
for(var i=0; i < options.length; i++){
if (options[i].checked) {
selected_device = options[i].value;
}
}
cookie.set('device', selected_device, {
domain: '<%= Rails.env.development? ? '' : Settings.host %>',
path : '/'
});
// window.location till varifrån du kom ifrån
window.location.assign(localStorage.getItem('referrer_href'));
alert('Okej, vi ställde in din enhet till ' + selected_device);
e.preventDefault();
return false;
});
</script>
</body>
När du har ställt in din CloudFront-distribution för att få webbsidor från din webbserver (aka origin i CloudFront-termer), kan du navigera till CloudFront-domänen via din webbläsare för att verifiera att CloudFront cachar dina webbsidor. I Chrome innebär det att navigera till fliken "Network" i utvecklarverktygen, klicka på den inledande sidbegäran och leta efter en huvudtext som heter "X-Cache". Huvudtextens värde kommer antingen vara "Hit from CloudFront" eller "Miss from CloudFront", beroende på om CloudFront hade den här speciella sidan/cookien-kombinationen cachad eller inte.
Om du vill använda den här tekniken i din egen rails-app har jag skapat en Rails-motor för att enkelt inkorporera. Se:
- https://github.com/milesmatthias/cdddn
- http://rubygems.org/gems/cdddn