• Nenhum resultado encontrado

Responda a cada um dos grupos em folhas separadas e devidamente identificadas. Grupo 1

N/A
N/A
Protected

Academic year: 2021

Share "Responda a cada um dos grupos em folhas separadas e devidamente identificadas. Grupo 1"

Copied!
7
0
0

Texto

(1)

Instituto Superior de Engenharia de Lisboa

Licenciatura em Engenharia Informática e de Computadores Programação na Internet

Semestre de Verão de 2015/2016 - 1ª Época (5 de Fevereiro de 2015) – Duração: 2 horas Responda a cada um dos grupos em folhas separadas e devidamente identificadas.

Grupo 1

1. [1] Considere o excerto de uma resposta HTTP apresentado em seguida:

HTTP/1.1 404 Not Found Server: SAPOttpd/2.0

Set-Cookie: uu=698b0f739d294727a8d9237c1e826995; Max-Age=3600; Expires=Thu, 04 Feb 2016 23:12:33 GMT; Path=/

Set-Cookie: uu=55cb5402a90a4894a44f786b96275f8f; Max-Age=3600; Expires=Thu, 12 Mar 2016 23:12:33 GMT; Path=/public

Content-Type: text/html; charset=UTF-8 Date: Thu, 04 Feb 2016 22:12:33 GMT Content-Length: 87537

Indique e justifique toda a informação que pode ser obtida desta resposta.

NOTA: A informação obtida, não se resume à enumeração do conteúdo do headers, mas sim às conclusões que se podem tirar da sua presença na resposta bem como dos seus valores.

Esta resposta corresponde a um pedido para um recurso que não existe no servidor (código 404). Foi servido pelo servidor SAPOttpd/2.0 (header Server) no dia 4/02/2016 às 22:12:33, hora GMT (header Date), o conteúdo da resposta está no formato text/html codificado em UTF-8 (header Content-Type) e tem 87537 bytes. A resposta inclui ainda 2 cookies para o mesmo domínio onde foi feito o pedido; um para a path “/” com o nome “uu” e valor “698b0f739d294727a8d9237c1e826995” válido até 04/02/2016, 23:12:33 hora GMT e que expira em 3600s. Outro para a path /public com o nome “uu” e valor “55cb5402a90a4894a44f786b96275f8f” válido até 12/03/2016, 23:12:33 hora GMT e que expira em 3600s.

2. [3] A função mapEachOf(obj, mapper, resultCallback), aplica a função mapper a cada propriedade de obj. A função mapper recebe como parâmetros: (1) o valor de uma propriedade; e (2) um

callback, que deve ser chamado com o resultado do mapeamento, ou o erro, caso exista (seguindo o idioma

Node.js para callbacks de funções assíncronas: (err, data) => void).

Quando ocorrer um erro, ou todos os mapeamentos tiverem concluídos, é chamada a função resultCallback, com o erro, ou um objeto que tem as mesmas propriedades de obj, com os valores transformados por mapper (seguindo o idioma Node.js para callbacks)

O código seguinte apresenta um exemplo de utilização da função mapEachOf. Neste exemplo todos as asserções são verificadas com sucesso.

var obj = { dev: "/dev.json", test: "/test.json", prod: "/prod.json" }; mapEachOf(obj, function (value, callback) {

setTimeout(_ => callback(null, value.toUpperCase()), 1000); }, function (err, resultObj) {

if (err) console.error(err.message); // configs is now a map of JSON data

console.log("resultObj: " + JSON.stringify(resultObj)); assert(resultObj.dev == "/DEV.JSON"); assert(resultObj.test == "/TEST.JSON"); assert(resultObj.prod == "/PROD.JSON"); })

(2)

function mapEachOf(obj, mapper, cb) {

var retObj = {};

let keys = Object.keys(obj); let count = keys.length; keys.forEach( function(key) {

mapper(obj[key], function (err, value) { if (err) { cb(err); return; } retObj[key] = value; if (--count == 0) { cb(null, retObj); } }) }); }

b. [1] Realize as alterações necessárias de modo a que a função possa ser chamada da seguinte forma:

obj.mapEachOf(function(value, callback) { ... },function(err, resultObj){...}); Object.prototype.mapEachOf = function (mapper, cb) {

mapEachOf(this, mapper, cb); }

3. [4] Considere o serviço http://api.super-soccer.org/ que disponibiliza uma API com os seguintes endpoints: • GET http://api.super-soccer.org/leagues/{league-id}/teams -- retorna as equipas que

constituem a liga identificada por league-id. O resultado JSON obedece ao esquema seguinte:

{“teams”: [{“name”: String, “teamId”: String}, …]}

• GET http://api.super-soccer.org/teams/{team-id} -- retorna informação da equipa identificada por team-id. O resultado JSON obedece ao esquema: {“name”: String, “shortName”: String, “squadMarketValue”: Number}

Implemente um módulo em Node.js, que exporta as seguintes funções (implemente as alíneas em conjunto reutilizando as funções utilitárias às várias alíneas):

Código auxiliar para as 3 alíneas do grupo:

const http = require('http') const API = {

hostname: 'localhost', port: 3001,

getTeamUri: (id) => '/teams/' + id,

getLeagueUri: (leagueId) => '/leagues/' + leagueId + '/teams', getTeamId: (leagueTeam) => leagueTeam.teamId

}

function httpGet(path, callback){

const opt = new Options(path)

const request = http.request(opt, resp => { let result = ''

resp.on('error', callback)

resp.on('data', data => result += data)

resp.on('end', () => { callback(null, JSON.parse(result)) }) }) request.on('error', callback) request.end(); } function Options(p, m) { this.hostname = API.hostname this.port = API.port this.method = m || 'GET' this.path = p }

a. [1] getTeam(teamId, callback) que passa a callback um objeto com as propriedades teamId, name,

shortName e squadMarketValue, da equipa identificada por teamId, ou erro em caso de falha.

(3)

function getTeam(id, callback) {

httpGet(API.getTeamUri(id), (err, obj) => { if(err) return callback(err);

callback(err, new Team(id, obj)) })

}

b. [2] getLeagueTeams(leagueId, callback) que passa a callback um array com as equipas constituintes da liga identificada por leagueId.

callback tem a assinatura: (err, [team, ...]) => void. function getLeagueTeams(leagueId, callback) {

const res = []

httpGet(API.getLeagueUri(leagueId), (err, league) => { if(err) { callback(err); return; }

const total = league.teams.length; league.teams.forEach(team => {

const teamId = API.getTeamId(team) getTeam(teamId, (err, team) => { res.push(team) if(res.length >= total){ callback(null, res) } }) }) }) }

c. [1] getLeagueMarketValue(leagueId, callback) que passa a callback o valor de mercado da liga identificada por leagueId.

callback tem a assinatura: (err, marketValue) => void.

O valor de mercado da liga é o somatório do valor de mercado das suas equipas.

function getMarketValue(leagueId, callback) {

getLeagueTeams(leagueId, (err, teams) => { if(err) { callback(err); return; }

const total = teams.reduce((prev, curr) => { return prev + curr.squadMarketValue }, 0)

callback(err, total) })

}

Grupo 2

1. [9] Implemente uma aplicação web em Node.js, com recurso aos módulos express, handlebars e o módulo desenvolvido na pergunta anterior.

a. [2] Implemente o endpoint: GET /league/{league-id}, que devolve uma view com uma tabela de 4 colunas (teamId, name, shortName e squadMarketValue) com as equipas da liga identificada por

league-id.

Este endpoint pode receber um parâmetro minValue na query string que específica o valor mínimo de mercado das equipas apresentadas, logo, serão excluídas equipas com squadMarketValue abaixo de minValue.

// leaguerate.js

const app = require('express')(); const soccerapi = require('./soccerapi')

const handlebars = require('express-handlebars').create({'defaultLayout': 'default'}) app.use(express.static(__dirname + '/public'))

(4)

app.get('/league/:id', (req, resp, next) =>{

soccerapi.getLeagueTeams(req.params.id, (err, teams) => { if(err) next(err)

else {

const filters = getFilterActions(req.params.id, teams) // Necessário para a alínea b) if(req.query.minValue) {

teams = teams.filter(team => team.squadMarketValue > req.query.minValue) }

resp.render('league', { 'teams': teams,

'teamsJson': JSON.stringify(teams),

'filters': filters // Necessário para a alínea b })

} }) })

// Necessário para a alínea b

function getFilterActions(leagueId, teams){

const values = teams.map(t => t.squadMarketValue) const maxVal = Math.max.apply(Math, values); const res = []

for (var index = 0; index < maxVal; index+=50000000) { res.push({

'label': (index/1000000) + 'M',

'href': '/league/' + leagueId + '?minValue=' + index }) } return res } // View league.handlebars <h1>Primeira Liga</h1>

<table class="table table-hover"> <thead> <tr> <th>Id</th> <th>ShortName</th> <th>Name</th> <th>Market Value</th> </tr> </thead> <tbody> {{#each teams}} <tr> <td>{{teamId}}</td> <td>{{shortName}}</td> <td>{{name}}</td> <td>{{squadMarketValue}}</td> </tr> {{/each}} </tbody> </table>

b. [2] Adicione à view a possibilidade de filtrar as equipas por valor de mercado. Para tal, a view deve apresentar N links com a legenda “> valor M”, em intervalos de 50.000.000 de euros até ao valor da equipa com maior cotação. Exemplo: “> 0M” “> 50M” “> 100M” “> 150M”. Cada link inclui o parâmetro

minValue com o respetivo valor.

// O código JavaScript no endpoint para esta alínea já consta em a) nas linhas assinaladas. // View league.handlebars. Acrescentar no início da view apresentada em a)

{{#each filters}}

<a class="btn btn-default" href={{href}}>{{label}}</a> {{/each}}

<hr />

c. [2] Adicione o necessário à view principal da aplicação para que inclua a seguinte UI:

O botão ADD adiciona à lista o shortName da equipa com o indentificador indicado em Team Id. ATENÇÃO: inclua os requisitos que necessitar para a alínea seguinte.

(5)

// View league.handlebars. Acrescentar no início da view apresentada em a) e b) <script src="/assets/js/leaguerateCtr.js"></script> <script> window.onload = function() { leaguerateCtr({{{teamsJson}}}) } </script> <hr />

<div class="form-group form-inline"> <label>Team Id: </label>

<input type="text" name="inTeamId" class="form-control" id="inTeamId" />

<input type="submit" value="ADD" onclick="leaguerateCtr.addTeam('inTeamId', 'selectTeams')"/> <select name="selectTeams" class="form-control" id="selectTeams" multiple>

<option></option> </select> // Código HTML da alínea d) </div> <hr /> // leaguerateCtr.js

var leaguerateCtr = function(teams) { leaguerateCtr = {

'addTeam': addTeam,

'group': group // Alínea d)

}

function addTeam(idOfTeamId, idOfSelectTeams) {

const teamId = document.getElementById(idOfTeamId).value const t = teams.find(team => team.teamId == teamId)

const selectTeams = document.getElementById(idOfSelectTeams) const optionTeam = document.createElement('option')

optionTeam.appendChild(document.createTextNode(t.name)) selectTeams.appendChild(optionTeam)

} }

d. [3]

d.1. Adicione também tudo o que for necessário à view principal, de modo a ter a seguinte UI e, quando clicado o botão GROUP, submeter um pedido AJAX para o URI /group, com a informação preenchida pelo utilizador (ver em seguida o formato da informação a enviar).

Implemente também endpoint para o pedido AJAX: POST /group, que recebe uma lista de identificadores de equipas e um nome a atribuir ao grupo formado por essas equipas (os grupos são mantidos apenas em memória na aplicação Node.js).

// View league.handlebars. Acrescentar à view apresentada em a) e b) e c) // no local marcado com o texto “Código HTML da alínea d)”

<label>Nickname: </label>

<input type="text" name='inNickname' id='inNickname'/>

<input type="submit" value="GROUP" onclick="leaguerateCtr.group('selectTeams', 'inNickname')" // leaguerateCtr.js Acrescentar o seguinte código

var leaguerateCtr = function(teams) { leaguerateCtr = {

'addTeam': addTeam, 'group': group

}

function group(idOfSelectTeams, idOfNickname) {

const selectTeams = document.getElementById(idOfSelectTeams) const nickname = document.getElementById(idOfNickname).value const teamsIds = []

for (var index = 0; index < selectTeams.length; index++) {

const t = teams.find(team => team.name === selectTeams[index].value) teamsIds.push(t.teamId)

}

(6)

}

function ajaxPost(path, obj) {

const xhttp = new XMLHttpRequest() xhttp.onreadystatechange = function() {

if (xhttp.readyState == 4 && xhttp.status == 200) { alert('Group created') } } xhttp.open("POST", path); xhttp.setRequestHeader("Content-type", "application/json"); xhttp.send(JSON.stringify(obj)); } }

// acrescentar no final de leaguerate.js const groups = {}

app.post('/group', (req, resp) => {

groups[req.body.nickname] = req.body.teamsIds console.log(groups)

resp.send(true); })

d.2. Implemente o endpoint GET /group/{nickname}, que apresenta as equipas do grupo

nickname, utilizando a view realizada em a).

// acrescentar no final de leaguerate.js app.get('/group/:nickname', (req, resp) =>{ const teams = []

const total = groups[req.params.nickname].length groups[req.params.nickname].forEach(id => { soccerapi.getTeam(id, (err, team) => { teams.push(team)

if(teams.length >= total) { resp.render('league', { 'teams': teams,

'teamsJson': JSON.stringify(teams),

'filters': filters // as we don't have a league id here, lets pass an empty array so no changes are needed in the views

}) } }) }) })

2. [3] Pretende-se qua a aplicação web anterior tenha suporte para browsers em desktops e browsers que estão em dispositivos móveis (smart phones, tablets, etc.). A versão da aplicação para dispositivos móveis tem os mesmos endpoints da aplicação para desktop, só que a path de cada URI é prefixada de /mobile. Exº: Se existir o recurso /home, o mesmo recurso para a versão mobile tem o uri /mobile/home.

a. [1,5] Desenvolva o necessário para que a aplicação detete clientes mobile que estão a tentar aceder a URIs cujas representações são vocacionadas para desktop e os redireciona para o correspondente recurso da versão para dispositivos móveis.

// Assumindo toda a iniciação de uma aplicação express tendo na variável app // o objeto que representa a aplicação

app.use(redirectMobile);

function redirectMobile(req, res, next) {

if(isMobileRequestToDesktopSite(req)) { res.redirect(getMobilePath(req.path)); return;

}

next(); // Otherwise call the next middleware }

// Funções auxiliares

function isMobileRequestToDesktopSite(req) {

function containsMobileWord(str) {

// if the user agent contains the "mobile" word is a mobile device return str.toLocaleLowerCase().indexOf("mobile") != -1;

}

// returns true if the path does not contains the "mobile" word and the user agent header contains // the "mobile" word, indicating the client is a mobile device

return !containsMobileWord(req.path) && containsMobileWord(req.header("User-Agent")) }

(7)

b. [1,5] Implemente uma solução alternativa que redireciona o utilizador para uma página onde este é questionado se pretende continuar a aceder à versão para desktop ou pretende ser redirecionado para a versão mobile, redirecionando-o para o recurso correspondente que estava a tentar aceder, consoante a sua resposta.

// Assumindo toda a iniciação de uma aplicação express com o middleware cookie-parser, // view engine handlebars e na variável app o objeto que representa a aplicação // e utilizando as funções auxiliaries de a)

const ASK_VERSION_URI = '/askversion'; app.use(askMobileDevices);

app.get(ASK_VERSION_URI, function(req, res) { let redirectFrom = req.query.redirectFrom; res.cookie("select-version", "selecting")

res.render('ask-version', { desktopUri: redirectFrom, mobileUri: getMobilePath(redirectFrom) }); })

function askMobileDevices(req, res, next) {

// get the state of the select-version cookie (set by this middleware once version is selected) var selectVer = req.cookies['select-version'];

if(!req.path.startsWith(ASK_VERSION_URI) && !selectVer && isMobileRequestToDesktopSite(req)) { res.redirect(ASK_VERSION_URI + "?redirectFrom=" + req.path)

return; }

next(); // Otherwise call the next middleware }

// View: ask-versions.handlebars <h1>

You are in a mobile device and trying to access to the desktop site version. </h1>

<div>

Do you want to proceed to the <a href={{desktopUri}}>desktop</a> optimized site, or be redirected to the <a href="{{mobileUri}}">mobile</a> version

</div>

Referências

Documentos relacionados

Como já destacado anteriormente, o campus Viamão (campus da última fase de expansão da instituição), possui o mesmo número de grupos de pesquisa que alguns dos campi

Este trabalho buscou, através de pesquisa de campo, estudar o efeito de diferentes alternativas de adubações de cobertura, quanto ao tipo de adubo e época de

Preliminarmente, alega inépcia da inicial, vez que o requerente deixou de apresentar os requisitos essenciais da ação popular (ilegalidade e dano ao patrimônio público). No

17 CORTE IDH. Caso Castañeda Gutman vs.. restrição ao lançamento de uma candidatura a cargo político pode demandar o enfrentamento de temas de ordem histórica, social e política

O enfermeiro, como integrante da equipe multidisciplinar em saúde, possui respaldo ético legal e técnico cientifico para atuar junto ao paciente portador de feridas, da avaliação

1595 A caracterização do repertório de habilidades sociais dos alunos do Grupo com Baixo Desempenho Acadêmico (GBD) e do Grupo com Alto Desempenho Acadêmico (GAD),

Podem treinar tropas (fornecidas pelo cliente) ou levá-las para combate. Geralmente, organizam-se de forma ad-hoc, que respondem a solicitações de Estados; 2)

A Tabela 3 apresenta os resultados de resistência ao impacto Izod e as caracterizações térmicas apresentadas em função dos ensaios de HDT, temperatura Vicat e a taxa de queima do