Статьи

Angular JS, Lift 3 и потоковые обещания

Простой AngularJS

В Lift всегда использовалась лучшая технология серверного проталкивания. Зачем? Он безопасен, хорошо работает с нестабильными соединениями, учитывает ограниченное количество HTTP-соединений между клиентом и сервером и многое другое.

Angular JS — это очень интересный пакет пользовательского интерфейса, который упрощает создание динамических одностраничных приложений, поскольку существует двусторонняя привязка между моделью и пользовательским интерфейсом, чтобы изменения в модели правильно отражались в пользовательском интерфейсе. И вся привязка является декларативной, поэтому, когда вы используете элемент модели в пользовательском интерфейсе, эта часть пользовательского интерфейса всегда обновляется при изменении модели.

Круглые поездки

Стандартный способ создания приложений Angular JS — связать определенные события в клиенте с вызовами REST к серверу, а когда вызовы REST удовлетворены, модель обновляется и пользовательский интерфейс перерисовывается. Звучит разумно.

Но есть проблемы:

  • Существует HTTP-соединение, открытое во время вызова REST, и, учитывая ограниченное количество доступных HTTP-соединений от браузера к данному хосту, одновременное открытие нескольких вызовов REST не является оптимальным.
  • REST хорошо работает для простых быстрых циклов запросов / ответов, но если вычисления занимают много времени, HTTP-соединение открыто в течение долгого времени, и это иногда портит прокси-серверы, которые часто задерживают HTTP-запросы через 90 секунд — 3 минуты.
  • REST не делает потоковую передачу хорошо
  • Разработчик должен защищать каждый вызов REST, потому что конечные точки REST всегда открыты, поэтому в каждый вызов должна быть встроена логика управления доступом

В Lift 3 мы представляем идею циклических поездок: запрос на стороне клиента, отправляемый на сервер, где клиент получает обещание потоковой передачи, сервер выполняет свои вычисления, а когда результаты готовы, они передаются клиенту… и если есть несколько результатов, несколько результатов передаются клиенту по мере того, как результаты становятся доступными.

Поездки Лифта туда и обратно имеют следующие преимущества перед простым ОТДЫХОМ:

  • Они потребляют гораздо меньше HTTP-соединений
  • Они используют технологию Lift Comet Server-Push, чтобы результаты передавались клиенту, даже если результаты вычисляются, пока клиент временно отключен и передается в порядке
  • В запросах используются средства Lift Ajax, которые включают в себя усовершенствованные механизмы повтора и дедупликации
  • Lift-json выполняет большую часть сериализации JSON Scala-land автоматически
  • Результаты передаются в потоковом режиме, поэтому результаты передаются с сервера на клиент по завершении вычислений на сервере.

Простое путешествие туда и обратно

Давайте посмотрим на реализацию простой циклической загрузки для загрузки и сохранения некоторых данных String. На стороне сервера у нас есть код, который выглядит следующим образом:

  // Save the text… we get a JSON blog and manually decode it
  // If an exception is thrown during the save, the client automatically
  // gets a Failure
  def doSave(info: JValue): JValue = {
     for {
       JString(path) <- info \ "path"
       JString(text) <- info \ "text"
     } {
       // save the text
     }
     JNull // a no-op
  }

  // Load the file
  def doLoad(fileName: String): String = {
    // load the named file, turn it into a String and return it
  }

// Associate the server functions with client-side functions
JScript(
JsCrVar("serverFuncs", sess.buildRoundtrip(List[RoundTripInfo](
"save" -> doSave _, "load" -> doLoad _)))

И чтобы вызвать код от клиента:

// Save the file
$scope.save = function() {
  serverFuncs.save({path: $scope.curFile, text: $scope.curText});
}

// load the named file and update the model
// when the file arrives
$scope.load = function(fn) {
  serverFuncs.load(fn).then(function(v) {
  $scope.$apply(function() {
    $scope.curFile = fn;
    $scope.curText = v;
   });
});
}

Икс

Таким образом, строки кода здесь очень похожи на строки кода для реализации REST.

Но у нас есть контроль доступа, потому что функции на стороне клиента на самом деле связаны с помощью GUID с функциями на стороне сервера, поэтому не нужно беспокоиться о CSRF или защите ресурса REST на основе разрешений пользователя, потому что единственные функции, которые клиент может вызвать, — это функции которые представлены ему.

Нам также не нужно явно настраивать обработку ошибок, потому что исключение на стороне сервера превратится в сбой на клиенте.

Но это потоки

Что происходит, когда вы хотите передавать данные с сервера на клиент?

У Lift’s Round Trips есть простой ответ, используйте Streams. Лифт автоматически определит, является ли возвращаемый тип buildRoundTripфункции Stream[T]«правильно». Итак, если наша функция на стороне сервера:

  def thing(s: String): Stream[String] = {
    var x = 0
    (s + x) #:: {
      Thread.sleep(1000);
      x += 1;
      s + x
    } #:: {
      Thread.sleep(1000);
      x += 1;
      s + x
    } #:: Stream.empty[String]
  }

И наш клиент выглядит так:

$scope.doThing = function(fn) {
  $scope.data = [];
  $scope.done = false;
  serverFuncs.thing(fn).then(function(v) {
  $scope.$apply(function() {
    $scope.data.push(v);
   }).done(function() {
   $scope.$apply(function() {$scope.done = true;})
   });
});
}

Таким образом, даже если данные поступают с промежутками в 1 секунду, данные передаются клиенту по мере их появления на сервере.

Это бесконечно гибкий

Иногда поток с исключениями для обозначения сбоя не является правильным ответом. Например, если у вас есть библиотека Iteratee, и вы хотели бы обернуть Round-Rate вокруг Iteratee, вам необходимо сообщить о новых данных, выполненных и сбое. RoundTripHandlerFuncявляется ответом для вас.

Вот необработанный код:

  private def thing(q: String, onChange: RoundTripHandlerFunc) {
    if (q.trim.length < 3) {
      onChange.failure("request too short!")
    } else {
      def doIt() {
        if (ran.nextFloat() < 0.05f) {
          onChange.done()
        } else if (ran.nextFloat() < 0.02f) {
          onChange.failure("Hey, I'm a failure")
        } else {

          onChange.send(/* some data */)
	            
          // delay without consuming a thread
          Schedule(doIt _, /* some timeout */)
        }
      }
	
      doIt()
    }
  }

Итак, приведенный выше код функционирует подобно Streamпримеру выше, но дает нам явный контроль над отправкой данных, выполнением и ошибкой.

Вы можете сделать неявное преобразование из любой формы потоковой библиотеки в RoundTripHandlerFuncвызов стиля и получить что-то вроде def myFunc(in: MyData): Iteratee[T]обслуживания через Round Trips.

Также обратите внимание, что типы ввода наших функций были Stringи JValue, но они могут быть любыми, если есть способ конвертировать JValueв этот тип через Lift-json.

Заворачивать

Здесь описывается, как функция Lift 3 Round Trip позволяет вам создавать действительно превосходные приложения AngularJS, поскольку вы можете легко передавать данные с клиента на сервер, вам не нужно беспокоиться о преобразовании типов данных, вам не нужно беспокоиться об управлении доступом и вам не нужно беспокоиться о разрыве соединения. На самом деле, вы не беспокоитесь о сантехнике, вы беспокоитесь о бизнес-логике вашего приложения.