my github
english | español
Waldo Urribarri HOME PROJECTS ABOUT ME


Solicitudes HTTP y cómo hacer una app de un sitio web sin API en Android

En Octubre de 2014 comencé un proyecto llamado MomoURU con el cual me decidí a realizar una app para ingresar al sistema de notas de la Universidad Rafael Urdaneta en mi ciudad natal. En ese momento mi esposa estudiaba en esa universidad, la cual no tenía una forma de acceder rápida y fácilmente a la información de los estudiantes, siendo la página web a demás bastante "rudimentaria". La idea sería hacer una app para ella, pero luego de ver que sería de gran utilidad para los demás estudiantes, decidimos lanzarla en la Play Store.

En el momento no sabía exactamente cómo lo haría, pero ahora con un ejemplo práctico detallaré los pasos que tomé. En este caso, haremos una app muy sencilla para entrar en tu cuenta de www.colourlovers.com.

Usando la consola de desarrolladores de Google Chrome, podemos ver mucho de lo que sucede "por debajo" de una solicitud a la página web. Al ver los headers de la solicitud y respuesta podemos ver los datos con los que tu explorador "conversa" con el servidor en cuestión al momento de entrar e iniciar sesión en la página. La idea sería replicar esto en nuestra aplicación. Con este método, se podría hacer una aplicación de "cualquier página" sin necesidad de que provean un API para dicho fin. Para esto usamos web scraping.

Ahora viene lo divertido.

De manera general, para entrar a la página los pasos serían:

- Entrar en "www.colourlovers.com" con una solicitud GET y obtener la cookie de la sesión.

- Hacer login en la página con una solicitud POST con la cookie obtenida.

- Luego de hacer login, obtener los datos de usuario (para esto entraremos en el perfil) con una solicitud GET.

Si entramos directamente a "http://www.colourlovers.com" nos encontraremos con algo parecido a esto:

Colourlovers headers

Usando estos datos crearemos las solicitudes desde una aplicación Android, y obtendremos además de los cookies el contenido entero de la página en HTML crudo.

Para esto crearemos una clase llamada HttpConn, la cual mantendrá nuestra "conexión" con la página web. Basado en los datos del primer request y response podemos crear estas constantes:

private static final int REQUEST_TIMEOUT = 10000;
private static final String ENCODING = "UTF-8";
private static final String ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
private static final String ACCEPT_ENCODING = "gzip, deflate, sdch";
private static final String ACCEPT_LANGUAGE = "en-US,en;q=0.8,es;q=0.6";
private static final String CONNECTION = "keep-alive";
private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36";
private static final String CONTENT_TYPE = "application/x-www-form-urlencoded";

Crearemos un método para las solicitudes GET.

    public String get(String url) throws ProtocolException, MalformedURLException, IOException {

        // Setup the new connection.
        URL obj = new URL(url);
        HttpURLConnection conn = (HttpURLConnection) obj.openConnection();

        // We need to set up all the params for the request.
        conn.setRequestMethod("GET");
        conn.setUseCaches(false); //no-cache
        conn.setConnectTimeout(REQUEST_TIMEOUT); // In case the URL is unavailable we use this timeout.
        conn.setReadTimeout(REQUEST_TIMEOUT);
        conn.setRequestProperty("Host", host);
        conn.setRequestProperty("User-Agent", USER_AGENT);
        conn.setRequestProperty("Accept", ACCEPT);
        conn.setRequestProperty("Accept-Language", ACCEPT_LANGUAGE);
        conn.setRequestProperty("Accept-Encoding", ACCEPT_ENCODING);
        conn.setRequestProperty("Connection", CONNECTION);

        // This is used to not mess with the cookies already grabbed on a previous request
        if (cookies != null) {
            for (String cookie : cookies) {
                conn.addRequestProperty("Cookie", cookie.split(";", 1)[0]);
            }
        }

        // Check if the response is Gzip encoded
        InputStream rinput = conn.getInputStream();
        BufferedReader in = null;
        List content_encoding = conn.getHeaderFields().get("Content-Encoding");
        if(content_encoding != null) {
            String enc = content_encoding.get(0);
            if(enc.equals("gzip"))
                in = new BufferedReader(new InputStreamReader(new GZIPInputStream(conn.getInputStream()), ENCODING));
            else
                in = new BufferedReader( new InputStreamReader(conn.getInputStream(), ENCODING) );
        } else {
            in = new BufferedReader( new InputStreamReader(conn.getInputStream(), ENCODING) );
        }

        String line;
        StringBuffer response = new StringBuffer();

        // Get whole html response.
        while ((line = in.readLine()) != null) {
            response.append(line);
        }
        in.close();

        // Save the last response code for further checking if needed.
        lastResponseCode = conn.getResponseCode();

        // We store the cookies.
        if (cookies == null) {
            List cooks = conn.getHeaderFields().get("Set-Cookie");
            if(cooks != null)
                cookies = cooks;
        }

        return response.toString();

    }

Luego de hacer el GET a la página principal, debemos hacer log in en la página con nuestro usuario. Al revisar de nuevo vi que la página hace un request a otra URL para mostrar el contenido del FORM donde ingresamos nuestras credenciales. La dirección es "https://www.colourlovers.com/ajax/header-log-in-form?r=http%3A%2F%2Fwww.colourlovers.com%2F". Posiblemente entonces podamos saltarnos el primer GET y hacer uno a este, lo cual hará más rápido el acceso (pues el contenido es más pequeño).

Usando de nuevo la consola podemos ver que al ingresar los datos y hacer click en Log In, se hace un request tipo POST a la URL "https://www.colourlovers.com/op/log-in/1" pasando los siguientes datos del form:

r:http%3A%2F%2Fwww.colourlovers.com%2F
userName:USUARIO
userPassword:NOTEMOSTRAREMICLAVE
x:29
y:12

Aquí vemos que hay dos variables X y Y, las cuales me parecieron extrañas. Al hacer un segundo log in obtuve:

r:http%3A%2F%2Fwww.colourlovers.com%2F
userName:USUARIO
userPassword: NOTEMOSTRAREMICLAVE
x:32
y:12

Como me imaginé, hay datos que cambian con cada request. Usualmente son por seguridad, pero en este caso no importan. De cualquier manera, estos datos deben encontrarse en el contenido HTML del primer request, y sólo debemos encontrarlos.

Colourlovers form data

Seguimos entonces con la creación del método POST con el cual enviaremos los datos de log in:

    public String post(String url, String referer, String postParams) throws ProtocolException, MalformedURLException, IOException {

        // Setup the new connection.
        URL obj = new URL(url);
        HttpURLConnection conn = (HttpURLConnection) obj.openConnection();

        // We need to set up all the params for the request.
        conn.setConnectTimeout(REQUEST_TIMEOUT);
        conn.setReadTimeout(REQUEST_TIMEOUT);
        conn.setUseCaches(false);
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Host", host);
        conn.setRequestProperty("User-Agent", USER_AGENT);
        conn.setRequestProperty("Accept", ACCEPT);
        conn.setRequestProperty("Accept-Language", ACCEPT_LANGUAGE);
        conn.setRequestProperty("Connection", CONNECTION);
        conn.setRequestProperty("Referer", referer);
        conn.setRequestProperty("Content-Type", CONTENT_TYPE);
        conn.setRequestProperty("Content-Length", Integer.toString(postParams.length()));

        // This is used to not mess with the cookies already grabbed on a previous request
        if (cookies != null) {
            for (String cookie : cookies) {
                conn.addRequestProperty("Cookie", cookie.split(";", 1)[0]);
            }
        }

        conn.setDoOutput(true);
        conn.setDoInput(true);

        // Send post params
        DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
        wr.writeBytes(postParams);
        wr.flush();
        wr.close();

        lastResponseCode = conn.getResponseCode();

        BufferedReader in = new BufferedReader( new InputStreamReader(conn.getInputStream(), ENCODING) );

        String line;
        StringBuffer response = new StringBuffer();

        while ((line = in.readLine()) != null) {
            response.append(line);
        }
        in.close();

        return response.toString();

    }

Luego de hacer el request del Login, ya podremos ingresar a zonas que sólo puedes ingresar si tienes un usuario válido. Lo probaremos con la URL "http://www.colourlovers.com/account" y extraeremos parte del texto que nos interesa.

Puedes ver el código completo en mi Github.

Final app

Ya con esto hemos visto cómo podemos tener acceso a datos de un usuario logueado a una web con nuestra app. En el caso de MomoURU las tareas simplemente fueron de procesar (en gran manera) las respuestas HTML, y listo. Tenemos una app funcional de cualquier sitio web. O bueno, en potencia :)


www.000webhost.com