Inlägg taggade ‘Java’

Hpricot och Sinatra på Google App Engine

Hpricot är en HTML parser som är skriven i Ruby. Jag gillar den eftersom den är snabb och extremt enkel att jobba med. Den är perfekt om man vill extrahera innehåll från en webbsida som inte tillhandahåller ett färdigt api. Det finns många bra tutorials på nätet.

Ett enkelt exempel

För att till exempel hitta alla nyheter på Athegas första sida kan man göra så här.

require 'rubygems'
require 'open-uri'
require 'hpricot'
 
# Läs in Athegas första sida
doc = Hpricot(open("http://athega.se"))
# Xpath uttryck för att hitta nyheterna
result = doc/"//*[@id='helplist']/li/a"

Hpricot på Google App Engine

Jag ville använda Hpricot tillsammans med Jruby och Sinatra (som Peter har skrivit mer om) på Google App Engine. Jag följde den här guiden för att komma igång med min Sinatra applikation på App Engine  och det gick smärtfritt. Tyvärr så small det direkt när jag försökte använda mig av Hpricot. Ett AccessControlException kastades.

javax.servlet.ServletContext log: Application Error
java.security.AccessControlException: access denied (java.net.SocketPermission athega.se resolve)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:323)
at java.security.AccessController.checkPermission(AccessController.java:546)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at com.google.appengine.tools.development.DevAppServerFactory$CustomSecurityManager.checkPermission(DevAppServerFactory.java:128)
at java.lang.SecurityManager.checkConnect(SecurityManager.java:1031)
at java.net.InetAddress.getAllByName0(InetAddress.java:1145)
at java.net.InetAddress.getAllByName(InetAddress.java:1083)
at java.net.InetAddress.getAllByName(InetAddress.java:1019)
at java.net.InetAddress.getByName(InetAddress.java:969)

Vilket tyder på att någon javaklass som inte är med på Googles lista över tillåtna klasser användes.  När jag studerade stacktracet lite närmare märkte jag att det var open-uri som ville använda javaklassen InetAddress som inte finns med i listan på godkända klasser.

Eftersom man med hjälp av Jruby kan ”scripta” java var det relativt enkelt att byta ut open-uri mot godkända javaklasser istället  och sedan automagiskt göra om java InputStream objektet till ett ruby io objekt som Hpricot kan ta i sin konstruktor. Lösningen blev enligt nedan.

require 'rubygems'
require 'hpricot'
# Importerar java istället för open-uri
require 'java'
# Skapa en instans av java-klassen URL
url = java.net.URL.new("http://athega.se")
# Kasta om java inputstreamen till ett ruby io objekt
ruby_io = org.jruby.RubyIO.new(JRuby.runtime, url.openStream)
io = Java.java_to_ruby(ruby_io.java_object)
# Sen är det bara att använda Hpricot som vanligt
doc = Hpricot(io)
result = doc/"//*[@id='helplist']/li/a"

Sedan transformerade jag resultatet till JSON och la upp applikationen här http://athega-news-api.appspot.com (OBS, applikationen returnerar JSON direkt så jag rekommenderar JSONView pluginet till Firefox om man vill titta på datan)

Om någon vill titta närmare på koden ligger den på Github men tänk på se till så att ni har tillstånd av rättighetsinnehavaren innan ni plockar data från webben.

Låt inte Tomcat jobba i onödan

Ett vanligt upplägg för en lastad sajt med någorlunda statiskt innehåll ser ut enligt nedan:

Upplägg, lastad sajt

Upplägg, lastad sajt

I korthet innebär det att den största delen av trafiken hanteras av enkla webbcachear som leverar sidor till slutanvändarna. Med ett visst intervall efterfrågar cachearna frontarna efter nytt innehåll. Frontarna kan exempelvis köra en eller flera Tomcat-instanser som genererar innehållet. På detta sätt kan även riktigt stora siter klara sig på ett tiotal maskiner.

För alla JSP-sidor som Tomcat-frontarna genererar åt cachearna skapas nu en session, precis som Servlet-standarden föreskriver. Det är helt i sin ordning, fast helt onödigt i det här fallet. Det genererade innehållet levereras inte direkt till slutanvändaren, utan till cachen. Kan man få servlet-motorn att inte skapa sessioner i onödan och på så sätt spara sina dyra resurser?

Ja, ett sätt är att överst i sin JSP-sida helt enkelt utbrista:

<%@page session="false"%>

Dock är det kanske inte att föredra i ett litet större projekt med hundratals JSP-sidor. Det man letar efter är ett sätt att slå av det centralt för alla JSP-sidor. Min första instikt var att konfiguerar Jasper, som kompilerar JSP-sidorna att inte skapa sessioner om man inte explicit bad om det. Någon sådan parameter hittade jag tyvärr inte. Personer som har liknande frågor på nätet får istället frågan; ”Varför vill du slå av sessioner? De är ju en del av standarden!”

Mitt nästa spår var att automatiskt försöka inkludera ovanstående direktiv för varje genererad sida. Från och med JSP 2.0 (som inte direkt kom ut igår) går det att få till ganska enkelt med något som kallas för implicit includes.

Börja med att lägga till ett jsp-config-direktiv i din web.xml:

<!-- Disable the use of sessions -->
<jsp-config>
  <jsp-property-group>
    <url-pattern>*.jsp</url-pattern>
    <include-prelude>/WEB-INF/jspf/disableSession.jspf</include-prelude>
  </jsp-property-group>
</jsp-config>

Detta matchar alla JSP-filer (med filändelsen .jsp) och inkluderar innehållet av filen disableSession.jspf precis som om du skulle ha skrivit

<%@include file="/WEB-INF/jspf/disableSession.jspf"%>

högst upp i varenda JSP-fil.

Notera att filen som inkluderas är av typen .jspf, ett JSP-fragment. Detta är nödvändigt och säkerställer bland annat att du inte råkar in i några oändliga inkluderingsloopar. Att filen ligger i mappen /WEB-INF/jspf är rekommenderat och förhindrar även direkt access för en slutanvändare.

Testa! Tomcat kommer att kasta fel i loggarna så fort du försöker skapa en session, men om du har det som jag är det precis så du vill ha det!