Да предположим, че посещавам уеб сайт. Щраквам с десния бутон върху една от навигационните връзки и избирам, за да отворя връзката в нов прозорец. Какво трябва да се случи? Ако съм като повечето потребители, очаквам новата страница да има същото съдържание, както ако щракнах директно върху връзката. Единствената разлика трябва да бъде, че страницата се появява в нов прозорец. Но ако вашият уебсайт е приложение с една страница (SPA), може да видите странни резултати, освен ако не сте планирали внимателно този случай.
как да получите jwt токен
Спомнете си, че в SPA, типичната навигационна връзка често е идентификатор на фрагмент, започвайки с хеш-знак (#). Щракването директно върху връзката не презарежда страницата, така че всички данни, съхранявани в променливите на JavaScript, се запазват. Но ако отворя връзката в нов раздел или прозорец, браузърът презарежда страницата, като инициализира всички променливи на JavaScript. Така че всички HTML елементи, обвързани с тези променливи, ще се показват по различен начин, освен ако не сте предприели стъпки по някакъв начин да запазите тези данни.
Има подобен проблем, ако изрично презареждам страницата, например чрез натискане на F5. Може би си мислите, че никога не би трябвало да се налага да натискам F5, защото сте настроили механизъм за автоматично изпращане на промените от сървъра. Но ако съм типичен потребител, можете да се обзаложите, че все пак ще презареждам страницата. Може би браузърът ми изглежда е пребоядисал екрана неправилно или просто искам да съм сигурен, че разполагам с най-новите котировки на акции.
За разлика от вътрешна заявка чрез RESTful API, взаимодействието на потребител с уебсайт не е без гражданство. Като потребител в мрежата мисля за посещението си на вашия сайт като за сесия, почти като телефонно обаждане. Очаквам браузърът да запомни данни за моята сесия, по същия начин, както когато се обадя на вашата линия за продажби или поддръжка, очаквам представителят да запомни казаното по-рано в разговора.
Очевиден пример за данни за сесията е дали съм влязъл в системата и ако да, като кой потребител. След като премина през екрана за вход, трябва да мога да навигирам свободно през специфичните за потребителя страници на сайта. Ако отворя връзка в нов раздел или прозорец и ми се покаже друг екран за вход, това не е много удобно за потребителя.
Друг пример е съдържанието на пазарската количка в е-търговия сайт. Ако натискането на F5 изпразни пазарската количка, потребителите вероятно ще се разстроят.
В традиционно многостранично приложение, написано на PHP , данните за сесията ще се съхраняват в суперглобалния масив $ _SESSION. Но в SPA това трябва да е някъде от страна на клиента. Има четири основни опции за съхраняване на данни за сесии в SPA:
Бисквитките са по-стара форма на уеб съхранение в браузъра. Първоначално те бяха предназначени да съхраняват данните, получени от сървъра, в една заявка и да ги изпращат обратно на сървъра в следващи заявки. Но от JavaScript можете да използвате бисквитки, за да съхранявате почти всякакъв вид данни, до ограничение на размера от 4 KB на бисквитка. AngularJS предлага модула ngCookies за управление на бисквитки. Има и пакет с js-бисквитки, който предоставя подобна функционалност във всяка рамка.
Имайте предвид, че всяка бисквитка, която създавате, ще бъде изпратена до сървъра при всяка заявка, независимо дали става въпрос за презареждане на страница или заявка на Ajax. Но ако основните данни за сесията, които трябва да съхранявате, са токенът за достъп за влезлия потребител, така или иначе искате това да се изпраща на сървъра при всяка заявка. Естествено е да се опитате да използвате тази автоматична трансмисия на бисквитки като стандартно средство за определяне на маркера за достъп за заявки на Ajax.
Може да спорите, че използването на бисквитки по този начин е несъвместимо с ПОЧИВКА архитектура. Но в този случай това е добре, тъй като всяка заявка чрез API все още е без гражданство, като има някои входове и някои изходи. Просто един от входовете се изпраща по забавен начин, чрез бисквитка. Ако можете да уредите заявката за API за вход също да изпрати маркера за достъп обратно в бисквитка, тогава вашият клиентски код едва ли изобщо трябва да се занимава с бисквитки. Отново, това е просто поредният изход от заявката, който се връща по необичаен начин.
как да пиша медийни заявки
Бисквитките предлагат едно предимство пред уеб съхранението. Можете да предоставите квадратче за отметка „запази ме влязъл“ във формуляра за вход. С семантиката очаквам, ако я оставя неотметнато, ще остана вписан, ако презаредя страницата или отворя връзка в нов раздел или прозорец, но гарантирано ще изляза, след като затворя браузъра. Това е важна функция за безопасност, ако използвам споделен компютър. Както ще видим по-късно, уеб хранилището не поддържа това поведение.
И така, как този подход може да работи на практика? Да предположим, че използвате LoopBack от страна на сървъра. Дефинирали сте персонален модел, разширявайки вградения потребителски модел, добавяйки свойствата, които искате да поддържате за всеки потребител. Вие сте конфигурирали Личност модел, който да бъде изложен над REST. Сега трябва да коригирате server / server.js, за да постигнете желаното поведение на бисквитките. По-долу е server / server.js, започвайки от това, което е генерирано от slc loopback, с маркираните промени:
var loopback = require('loopback'); var boot = require('loopback-boot'); var app = module.exports = loopback(); app.start = function() { // start the web server return app.listen(function() { app.emit('started'); var baseUrl = app.get('url').replace(//$/, ''); console.log('Web server listening at: %s', baseUrl); if (app.get('loopback-component-explorer')) { var explorerPath = app.get('loopback-component-explorer').mountPath; console.log('Browse your REST API at %s%s', baseUrl, explorerPath); } }); }; // start of first change app.use(loopback.cookieParser('secret')); // end of first change // Bootstrap the application, configure models, datasources and middleware. // Sub-apps like REST API are mounted via boot scripts. boot(app, __dirname, function(err) { if (err) throw err; // start of second change app.remotes().after('Person.login', function (ctx, next) { if (ctx.result.id) { var opts = {signed: true}; if (ctx.req.body.rememberme !== false) { opts.maxAge = 1209600000; } ctx.res.cookie('authorization', ctx.result.id, opts); } next(); }); app.remotes().after('Person.logout', function (ctx, next) { ctx.res.cookie('authorization', ''); next(); }); // end of second change // start the server if `$ node server.js` if (require.main === module) app.start(); });
Първата промяна конфигурира анализатора на бисквитки да използва „тайна“ като тайна за подписване на бисквитки, като по този начин активира подписани бисквитки. Трябва да направите това, защото въпреки че LoopBack търси маркер за достъп в някоя от „упълномощаването“ или „access_token“ на бисквитките, изисква такава бисквитка да бъде подписана. Всъщност това изискване е безсмислено. Подписването на бисквитка има за цел да гарантира, че бисквитката не е модифицирана. Но няма опасност да промените маркера за достъп. В крайна сметка можете да изпратите маркера за достъп в неподписана форма като обикновен параметър. По този начин не е нужно да се притеснявате, че тайната за подписване на бисквитки е трудно да се отгатне, освен ако не използвате подписани бисквитки за нещо друго.
Втората промяна настройва някои последващи процеси за методите Person.login и Person.logout. За Човек.влизам , искате да вземете получения токен за достъп и да го изпратите на клиента като подписано „бисквитка“ „упълномощаване“ също. Клиентът може да добави още едно свойство към параметъра за идентификационни данни, запомнете ме, като посочва дали да направи бисквитката постоянна в продължение на 2 седмици. По подразбиране е вярно. Самият метод за вход ще игнорира това свойство, но постпроцесорът ще го провери.
За Person.logout , искате да изчистите тази бисквитка.
Можете да видите резултатите от тези промени веднага в StrongLoop API Explorer. Обикновено след искане на Person.login ще трябва да копирате маркера за достъп, да го поставите във формуляра горе вдясно и да щракнете върху Задаване на маркер за достъп. Но с тези промени не е нужно да правите нищо от това. Токенът за достъп се запазва автоматично като „упълномощаване“ на бисквитката и се изпраща обратно при всяка следваща заявка. Когато Explorer показва заглавията на отговорите от Person.login, той пропуска бисквитката, тъй като на JavaScript никога не е позволено да вижда заглавките Set-Cookie. Но бъдете сигурни, че бисквитката е там.
От страна на клиента, при презареждане на страница, ще видите дали „упълномощаването“ на бисквитките съществува. Ако е така, трябва да актуализирате записа си за текущия userId. Вероятно най-лесният начин да направите това е да съхраните userId в отделна бисквитка при успешно влизане, за да можете да го извлечете при презареждане на страница.
Докато посещавам уеб сайт, който е внедрен като SPA, URL адресът в адресната лента на браузъра ми може да изглежда нещо като „https://example.com/#/my-photos/37“. Частта с идентификатор на фрагменти от това, „# / my-photos / 37“, вече е колекция от държавна информация, която може да се разглежда като сесийна информация. В този случай вероятно разглеждам една от снимките си, тази, чийто идентификационен номер е 37.
Можете да решите да вградите други данни за сесията в идентификатора на фрагмента. Спомнете си, че в предишния раздел, с маркера за достъп, съхраняван в „упълномощаването“ на бисквитката, все пак трябваше да следите някак userId. Единият вариант е да го съхранявате в отделна бисквитка. Но друг подход е да го вградите в идентификатора на фрагмента. Можете да решите, че докато съм влязъл, всички страници, които посещавам, ще имат идентификатор на фрагменти, започващ с „# / u / XXX“, където XXX е userId. Така че в предишния пример идентификаторът на фрагменти може да бъде „# / u / 59 / my-photos / 37“, ако моят userId е 59.
Теоретично можете да вградите самия токен за достъп в идентификатора на фрагмента, като избягвате всякаква нужда от бисквитки или уеб съхранение. Но това би било лоша идея. Токенът ми за достъп ще бъде видим в адресната лента. Всеки, който гледа през рамото ми с камера, може да направи моментна снимка на екрана, като по този начин получи достъп до акаунта ми.
Последна бележка: възможно е да се създаде SPA, така че изобщо да не се използват идентификатори на фрагменти. Вместо това използва обикновени URL адреси като „http://example.com/app/dashboard“ и „http://example.com/app/my-photos/37“, като сървърът е конфигуриран да връща HTML от най-високо ниво за вашия SPA в отговор на заявка за някой от тези URL адреси. След това вашият SPA прави маршрута си въз основа на пътя (например „/ app / dashboard“ или „/ app / my-photos / 37“) вместо идентификатора на фрагмента. Той прихваща кликванията върху навигационните връзки и използва History.pushState () за да натиснете новия URL адрес, след което продължава с маршрутизиране, както обикновено. Той също така прослушва събития на popstate, за да открие потребителя, щракнал бутона за връщане, и отново продължава с маршрутизиране на възстановения URL адрес. Пълните подробности за това как да се приложи това са извън обхвата на тази статия. Но ако използвате тази техника, тогава очевидно можете да съхранявате данни за сесията в пътя вместо идентификатора на фрагмента.
Уеб съхранението е механизъм за JavaScript за съхраняване на данни в браузъра. Подобно на бисквитките, уеб съхранението е отделно за всеки произход. Всеки съхраняван елемент има име и стойност, и двете са низове. Но уеб съхранението е напълно невидимо за сървъра и предлага много по-голям капацитет за съхранение от бисквитките. Има два вида уеб съхранение: локално съхранение и съхранение на сесии.
Елемент от локално хранилище е видим във всички раздели на всички прозорци и продължава дори след затваряне на браузъра. В това отношение той се държи донякъде като бисквитка със срок на годност много в бъдеще. По този начин е подходящ за съхраняване на токен за достъп в случая, когато потребителят е поставил отметка „да ме държиш в системата“ във формуляра за вход.
Елемент от хранилището на сесията се вижда само в раздела, където е създаден, и изчезва, когато този раздел е затворен. Това прави живота му много различен от този на всяка бисквитка. Спомнете си, че бисквитката на сесията все още се вижда във всички раздели на всички прозорци.
Ако използвате AngularJS SDK за LoopBack , клиентската страна автоматично ще използва уеб съхранение, за да запази както маркера за достъп, така и userId. Това се случва в услугата LoopBackAuth в js / services / lb-services.js. Той ще използва локално хранилище, освен ако параметърът RememberMe не е фалшив (обикновено означава, че квадратчето „остави ме влязъл в системата“ не е отметнато), в който случай ще използва съхранение на сесия.
Резултатът е, че ако вляза с отметка „остави ме влязъл“ и след това отворя връзка в нов раздел или прозорец, няма да вляза там. Най-вероятно ще видя екрана за вход. Можете сами да решите дали това е приемливо поведение. Някои може да го сметнат за приятна функция, където можете да имате няколко раздела, всеки от които е влязъл като различен потребител. Или може да решите, че едва ли някой вече използва споделени компютри, така че можете просто да пропуснете квадратчето за отметка „остави ме влязъл в системата“.
И така, как би изглеждало обработването на данните за сесията, ако решите да използвате AngularJS SDK за LoopBack? Да предположим, че имате същата ситуация като преди от страна на сървъра: дефинирали сте модел на човек, разширявайки модела на потребителя и сте изложили модела на човек над REST. Няма да използвате бисквитки, така че няма да имате нужда от нито една от описаните по-рано промени.
От страна на клиента, някъде във вашия най-външен контролер, вероятно имате променлива като $ scope.currentUserId, която съдържа userId на текущо влезлия потребител или null, ако потребителят не е влязъл. След това, за да се справите правилно с презареждането на страницата, вие просто включете този израз в конструкторската функция за този контролер:
$scope.currentUserId = Person.getCurrentId();
Това е толкова лесно. Добавете „Person“ като зависимост на вашия контролер, ако още не е.
IndexedDB е по-ново средство за съхраняване на големи количества данни в браузъра. Можете да го използвате за съхраняване на данни от всякакъв тип JavaScript, като обект или масив, без да се налага да ги сериализирате. Всички заявки срещу базата данни са асинхронни, така че получавате обратно обаждане, когато заявката е завършена.
капиталовото бюджетиране е процесът на:
Можете да използвате IndexedDB за съхраняване на структурирани данни, които не са свързани с никакви данни на сървъра. Пример може да бъде календар, списък със задачи или запазени игри, които се играят локално. В този случай приложението наистина е локално и вашият уебсайт е само средство за доставянето му.
Понастоящем Internet Explorer и Safari имат само частична поддръжка за IndexedDB. Други основни браузъри го поддържат напълно. Едно сериозно ограничение в момента обаче е, че Firefox деактивира IndexedDB изцяло в режим на частно сърфиране.
Като конкретен пример за използване на IndexedDB, нека вземем приложение за плъзгащи се пъзели от Pavol Daniš и го променете, за да запазите състоянието на първия пъзел, Basic 3x3 плъзгащ се пъзел, базиран на логото на AngularJS, след всеки ход. Презареждането на страницата ще възстанови състоянието на този първи пъзел.
Създадох a вилица на хранилището с тези промени, всички от които са в app / js / puzzle / slidingPuzzle.js. Както можете да видите, дори елементарното използване на IndexedDB е доста свързано. Ще покажа само акцентите по-долу. Първо, функцията за възстановяване се извиква по време на зареждането на страницата, за да отвори базата данни IndexedDB:
/* * Tries to restore game */ this.restore = function(scope, storekey) { this.storekey = storekey; if (this.db) { this.restore2(scope); } else if (!window.indexedDB) { console.log('SlidingPuzzle: browser does not support indexedDB'); this.shuffle(); } else { var self = this; var request = window.indexedDB.open('SlidingPuzzleDatabase'); request.onerror = function(event) { console.log('SlidingPuzzle: error opening database, ' + request.error.name); scope.$apply(function() { self.shuffle(); }); }; request.onupgradeneeded = function(event) { event.target.result.createObjectStore('SlidingPuzzleStore'); }; request.onsuccess = function(event) { self.db = event.target.result; self.restore2(scope); }; } };
The request.onupgradeneeded event обработва случая, когато базата данни все още не съществува. В този случай ние създаваме обектното хранилище.
След като базата данни е отворена, функцията възстановяване2 се извиква, което търси запис с даден ключ (който всъщност ще бъде константата „Basic“ в този случай):
/* * Tries to restore game, once database has been opened */ this.restore2 = function(scope) { var transaction = this.db.transaction('SlidingPuzzleStore'); var objectStore = transaction.objectStore('SlidingPuzzleStore'); var self = this; var request = objectStore.get(this.storekey); request.onerror = function(event) { console.log('SlidingPuzzle: error reading from database, ' + request.error.name); scope.$apply(function() { self.shuffle(); }); }; request.onsuccess = function(event) { if (!request.result) { console.log('SlidingPuzzle: no saved game for ' + self.storekey); scope.$apply(function() { self.shuffle(); }); } else { scope.$apply(function() { self.grid = request.result; }); } }; }
Ако такъв запис съществува, неговата стойност замества решетъчния масив на пъзела. Ако има грешка при възстановяването на играта, просто разбъркваме плочките както преди. Имайте предвид, че мрежата е 3x3 масив от плочки обекти, всеки от които е доста сложен. Голямото предимство на IndexedDB е, че можете да съхранявате и извличате такива стойности, без да се налага да ги сериализирате.
пътищата, по които продуктите или услугите достигат до клиентите, са известни като
Ние използваме $ кандидатстват да информира AngularJS, че моделът е променен, така че изгледът ще бъде актуализиран по подходящ начин. Това е така, защото актуализацията се случва в обработчик на събития на DOM, така че AngularJS иначе не би могъл да открие промяната. Всяко приложение на AngularJS, използващо IndexedDB, вероятно ще трябва да използва $ apply по тази причина.
След всяко действие, което би променило решетъчния масив, като преместване от потребителя, се извиква функцията save, която добавя или актуализира записа със съответния ключ, въз основа на актуализираната стойност на мрежата:
/* * Tries to save game */ this.save = function() { if (!this.db) { return; } var transaction = this.db.transaction('SlidingPuzzleStore', 'readwrite'); var objectStore = transaction.objectStore('SlidingPuzzleStore'); var request = objectStore.put(this.grid, this.storekey); request.onerror = function(event) { console.log('SlidingPuzzle: error writing to database, ' + request.error.name); }; request.onsuccess = function(event) { // successful, no further action needed }; }
Останалите промени са за извикване на горните функции в подходящо време. Можете да прегледате ангажирам показва всички промени. Имайте предвид, че призоваваме възстановяване само за основния пъзел, а не за трите разширени пъзела. Ние използваме факта, че трите разширени пъзела имат атрибут api, така че за тези просто правим нормалното разбъркване.
Ами ако искаме да запазим и възстановим и разширените пъзели? Това ще изисква известно преструктуриране. Във всеки от усъвършенстваните пъзели потребителят може да регулира файла с източника на изображението и размерите на пъзела. Така че ще трябва да подобрим стойността, съхранявана в IndexedDB, за да включим тази информация. По-важното е, че ще ни трябва начин да ги актуализираме от възстановяване. Това е малко за този и без това дълъг пример.
В повечето случаи уеб съхранението е най-добрият ви залог за съхраняване на данни за сесията. Напълно се поддържа от всички основни браузъри и предлага много по-голям капацитет за съхранение от бисквитките.
Бихте използвали бисквитки, ако вашият сървър вече е настроен да ги използва или ако имате нужда данните да бъдат достъпни във всички раздели на всички прозорци, но също така искате да сте сигурни, че те ще бъдат изтрити, когато браузърът бъде затворен.
Вече използвате идентификатора на фрагменти, за да съхранявате данни за сесията, които са специфични за тази страница, като например идентификатора на снимката, която потребителят гледа. Въпреки че можете да вградите други данни за сесията в идентификатора на фрагмента, това всъщност не предлага никакво предимство пред уеб хранилището или бисквитките.
Използването на IndexedDB вероятно ще изисква много повече кодиране от която и да е от другите техники. Но ако стойностите, които съхранявате, са сложни JavaScript обекти, които биха били трудни за сериализиране или ако имате нужда от транзакционен модел , тогава може да си струва.