Aplicación web en Go: Tiempos de espera autobús en distintas paradas.

Siguiendo con el artículo de la Api de la EMT Madrid, vamos a ver cómo hacer un servidor de aplicaciones en Go para móvil.

Entorno de programación

Lo primero es configurar nuestro editor. En otros casos, yo he usado sublime text para editar Go. En este caso, he optado por eclipse y el plugin para go:

https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation

En el servidor de desarrollo, instalamos go:

 wget -c https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz
tar -C /usr/local -xzf go*.tar.gz
 echo export PATH=$PATH:/usr/local/go/bin >> /etc/profile
# DEBIAN:
export PATH=$PATH:/usr/local/go/bin >> /etc/bash.bashrc

Crear la aplicación web

Recomiendo leer el tutorial de Google para comenzar a escribir aplicaciones web.

 mkdir myproj
mkdir myproj/src
mkdir myproj/doc
# Creamos el archivo con la aplicación web:
touch myproj/src/busdm.go

Para compilar

go build  myproj/src/busdm.go
go run myproj/src/busdm.go

Para ver los resultados, si hemos usado el puerto 8080:

http://localhost:8080/

El algoritmo

El algoritmo es bastante sencillo suma a los autobuses el tiempo andando, y da el menor tiempo posible. Quita todos aquellos autobuses que llegarán a parada antes de que lleguemos andando.

Métodos:

  • Decide: Devuelve la parada hacia la que hay que andar y el autobús
  • GetStopTime (IdStop): devuelve el tiempo del menor autobús. Hace la llamada al servicio de la EMT, y devuelve una lista (IdStop, IdBus, Time).
  • GetWalkingTime (IdStop): devuelve el tiempo que nos cuesta llegar andando.

GetStopTime

 Estructura JSON

Desde el ejemplo, se puede sacar la estructura JSON para hacer el parsing  http://json2struct.mervine.net/

GetWalkingTime

 

Decide

Servicios web EMT (Madrid) : Uso de Api

Vamos a ver cómo empezar a trabajar con los servicios web que pone a nuestra disposición EMT Madrid.

En nuestro caso, queremos hacer sobre la Api de la EMT, un gestor muy sencillo, que tenga en cuenta, además, del tiempo estimado de al autobús a la parada, nuestro tiempo andando a varias paradas distintas, de cara a decidir hacia qué parada debemos caminar.

Toda la información de los métodos disponibles están en:

http://opendata.emtmadrid.es/Servicios-web/GEO

En nuestro caso, vamos a usar la llamada Get Arrive Stop con los números de parada.

Get Arrive Stop:     https://openbus.emtmadrid.es:9443/emt-proxy-server/last/geo/GetArriveStop.php

El formato de respuesta es una lista de Arrives

{"arrives":[{"stopId":608,"lineId":"25","isHead":"False","destination":"CASA DE CAMPO","busId":"4063","busTimeLeft":0,"busDistance":0,"longitude":-3.7207142044687,"latitude":40.415443621459,"busPositionType":0},{"stopId":608,"lineId":"39","isHead":"False","destination":"SAN IGNACIO","busId":"4105","busTimeLeft":163,"busDistance":815,"longitude":-3.7319667753991,"latitude":40.412296871723,"busPositionType":1},{"stopId":608,"lineId":"25","isHead":"False","destination":"CASA DE CAMPO","busId":"4073","busTimeLeft":372,"busDistance":1604,"longitude":-3.7124736587359,"latitude":40.419499463385,"busPositionType":1},{"stopId":608,"lineId":"39","isHead":"False","destination":"SAN IGNACIO","busId":"4111","busTimeLeft":417,"busDistance":1833,"longitude":-3.7127886611517,"latitude":40.420772982078,"busPositionType":1},{"stopId":608,"lineId":"138","isHead":"False","destination":"ALUCHE","busId":"4182","busTimeLeft":537,"busDistance":2014,"longitude":-3.7112167000793,"latitude":40.423116470486,"busPositionType":1},{"stopId":608,"lineId":"33","isHead":"False","destination":"CASA DE CAMPO","busId":"4234","busTimeLeft":920,"busDistance":2745,"longitude":-3.73103140059,"latitude":40.41264537381,"busPositionType":1},{"stopId":608,"lineId":"138","isHead":"False","destination":"ALUCHE","busId":"4181","busTimeLeft":999999,"busDistance":3191,"longitude":-3.7266414712365,"latitude":40.411397511188,"busPositionType":1},{"stopId":608,"lineId":"33","isHead":"False","destination":"CASA DE CAMPO","busId":"4232","busTimeLeft":999999,"busDistance":7818,"longitude":-3.7622555004248,"latitude":40.404643143841,"busPositionType":1}]}


En la documentación de la API se pueden ver el resultado

stopId Stop Id int
lineId Line Id string
isHead true|false Stop is header for this line string
destination Destination text string
busId Bus id string
busTimeLeft Time to arrive in seconds (if all 9, means more than 20 minutes) (if 0 bus in the stop) int
busDistance Distance in meters int
latitude Latitude in decimal degrees double
longitude Longitude in decimal degrees double
busPositionType Real or estimate int

Los únicos campos que vamos a usar nosotros son:

  • StopID
  • lineID
  • busTimeLeft.    Todo 9 maś de 20 minutos. 0 -> Autobús en parada.

Esta llamada, devuelve, para cada parada, el tiempo de los dos autobuses más cercanos.

 

Crear un servicio web REST con ASP 1.0 clásico

Evidentemente, la tecnología escogida no es una nuestra elección; viene dada por los requisitos, y, a pesar de todas las justificaciones esgrimidas no ha sido posible cambiarlo.

Servidor web

Nuestro servidor web es un W 2003 con las características IIS activadas.

En la carpeta INETPUB, hemos creado como primer paso una carpeta para javascript donde hemos puesto las librerías JSON y JQUERY (en versión 1).

Recoger parámetros en Javascript desde la URL

La url que se invoca es algo como:

http://www.anyserver.com/mypage.asp?Details=products

En nuestra página ASP, vamos a usar las funciones de purl para sacar valores de la línea de parámetros

Verificar parámetros con expresiones regulares.

...
<script src="js/purl.js" language="javascript" type="text/javascript"></script>
...
<script type="text/javascript">
ip=$.url().param("ip") </script>

Coger parámetros en Classic ASP 1.0 desde url o cabecera

Esta es la forma de recogerlo de URL

IP = Request.QueryString("IP")

Si hay una cabecera propia, hay que anteponer http_ para poder extraerla, cómo explican en el foro de microsoft:

If a client request includes a header other than those specified in the IIS Server Variables table, you can retrieve the value of that header by preceding the header name with “HTTP_” in the call to Request.ServerVariables. For example, if the client sends a header such as SomeNewHeader:SomeNewValue, you can retrieve SomeNewValue by using the following synta

<% Request.ServerVariables("HTTP_SomeNewHeader") %>

Nosotros nos hemos hecho una función, para aceptar parámetros por url y por cabecera indistintamente:

Function GetParam (strTarget)
Dim param
param = Request.ServerVariables("HTTP_"&strTarget)
if (Len(param) = 0) then
param = Request.QueryString (strTarget)
End if
GetParam=param 'Return value
End Function

Respuesta del servidor

Todas las respuestas se hacen en forma de texto:

response.write Linea 1 & vbcrlf
response.write Linea 2 & vbcrlf

Gestión de logs desde Classic ASP 1.0
Hemos usado el código de Digital Colony, por que es inmejorable.

En la carpeta donde se guardan los logs, se debe dar permisos al usuario ASPNET de lectura y escritura.

Conexión a base de datos desde Classic ASP 1.0

Vamos a usar esta solución para prevenir inyección de sql en la base de datos.

Además, hemos creado un usuario con privilegios sólo de escritura sobre la base de datos, para prevenir cambios o consultas sobre otras base de datos.

strConnect = "DRIVER={SQL Server};SERVER=server\instance;DATABASE=my_Db;UID=my_uid;PWD=my_pwd;"
Set conn = Server.CreateObject("ADODB.Connection")
conn.ConnectionString = strConnect
conn.Open
(...)
conn.Close

Transacciones en base de datos desde ASP 1.0

Algunas de nuestras conexiones, las vamos a hacer dentro de una transacción:

conn.BeginTrans
(...)
conn.CommitTrans

 Función ExecuteScalar contra la base de datos desde Classic ASP 1.0

Hemos creado una función para recoger el primer resultado de una sentencia select.

Function executeScalar (strCommand)
Set command = CreateObject("ADODB.Command")
command.ActiveConnection = conn
command.CommandText = strCommand
Set rs = command.Execute ()
if not rs.EOF then
rs.movefirst
executeScalar = rs.fields(0)
else
executeScalar  = 0
End IF
Set rs = nothing
Set command = nothing
End Function

Gestión de errores de ASP

Para nuestro servicio Rest, hemos escogido la gestión más simple de las que proponen desde  codeguru:

En la cabecera de la página ASP:
<%
' Error Handler
' Turn on page buffering:
Response.Buffer = True
' Turn On Error Handling:
' On Error Resume Next so that errors don't stop page execution
On Error Resume Next
%>

(…) Código ASP de la página

Al final, se captura el error, se limpia la respuesta y se responde con el texto que se espere.

<%
' Error Handler
If Err.Number <> 0 Then
'Clear response buffer
Response.Clear
End If
%>

Más información

Vamos a comenzar por repasar los siguientes foros de información: