ElasticSearch als Suchmaschine für die eigene Website / Grails als Proxy - Failover::Wiki

ElasticSearch sollte nicht direkt aus dem Web erreichbar sein. Ein vorgeschalteter ApplicationServer sichert den Zugriff ab.

Grails ist schnell installiert, ein HowTo gibt es auf der Download-Seite.

Installation

Da für die aktuelle Version 3.x noch nicht alle/ausreichend Plugins verfügbar sind, sollte eine 2er Version installiert werden, z.B. 2.5.0.

apt-get install openjdk-7-jdk

export JAVA_HOME=/usr/lib/jvm/default-java

curl -s get.gvmtool.net | bash
source "$HOME/.gvm/bin/gvm-init.sh"
gvm install grails 2.5.0

export GRAILS_HOME="$HOME/.gvm/grails/current/"

export PATH=$GRAILS_HOME/bin:$PATH

grails -version

Projekt erstellen

Ein neues Projekt ist schnell erstellt:

grails create-app elastic
cd elastic

Unnötiges entfernen

Da hier nur eine Art HTTP-Proxy erstellt wird, ist kein Daten-Backend notwendig:

sed -i -e s/^/\/\// grails-app/conf/DataSource.groovy

Plugin(s) installieren

Die Connection zu ElasticSearch soll das Grails-Rest-Plugin übernehmen:

sed -i -e '/plugins {/a compile ":rest:0.8"' grails-app/conf/BuildConfig.groovy

Die Application schreiben

Nun muss nur noch der Controller selbst geschrienen werden:

grails-app/controllers/elastic/ElasticController.groovy

package elastic

import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.Method.GET
import static groovyx.net.http.Method.POST
import static groovyx.net.http.ContentType.TEXT
import static groovyx.net.http.ContentType.JSON

@grails.validation.Validateable
class Elastic {

  String  query
  Integer from = 0
  Integer size  = 5 // elasticsearch: size

  static constraints = {
    query size: 3..100, blank: false
    from max:100
    size max:10
  }//constraints

}//Elastic

class ElasticController {

  static allowedMethods = [index:['GET','POST']]

  def search(Elastic elastic) {

    if (!elastic.validate()) { render status:400; return; }
    
    def http = new HTTPBuilder( 'http://localhost:9200' )
    http.request( POST, JSON ) { req ->
      uri.path = '/wiki/_search'//?size=0'
      uri.query= [
        size  : elastic?.size,
        pretty: 0,
        from  : elastic?.from,
        fields: "abstract,author,date,keywords,title,url"
      ]
      body     = [
        query  : [
          simple_query_string : [
            query           : elastic?.query,
            default_operator: "and"
          ]
        ]
      ]
      response.success = { resp, json ->
        render(contentType: "application/json"){ json.hits }
      }//response.success
    }//http.request
  }//index
}//ElasticController

Testen

Um das ganze zu testen, muss der Grails-App-Server gestartet werden:

grails run-app

Testen kann man nun z.B. mit curl:

# JSON POST:
curl -H 'Content-Type: application/json' -H'X-Requested-With: XMLHttpRequest' -i -XPOST  http://localhost:8080/elastic/elastic/search -d '{"query":"foo +bar"}'
# URL-encoded POST
curl -i -XPOST  http://localhost:8080/elastic/elastic/search -d 'query=foo +bar&size=5&from=0'
# GET
curl -i -XGET  'http://localhost:8080/elastic/elastic/search?query=foo%20+bar&from=1&size=1'

WAR-File für den Produktiv-Einsatz

Wenn alles funktioniert, nur noch ein WAR-File für den Tomcat bauen und auf den Produktiv-Server deployen:

grails prod war

Application-Server absichern

Natürlich soll auch der Application-Server nicht direkt aus dem Web erreichbar sein. Einfach einen NginX oder ähnliches aul Proxy vorschalten:

/etc/nginx/sites-enabled/mysite

...
  location /search/ {

    if ($request_method !~ ^(GET|HEAD|POST)$ ){ return 405; }
    if ($request_method ~ ^(HEAD)$ ){ return 200 '{"status":200}'; }
    disable_symlinks on;

    include /etc/nginx/proxy_params;
    proxy_intercept_errors on;
    proxy_pass http://127.0.0.1:8080/elastic/elastic/search;
    proxy_redirect default;
  }
...
curl -H 'Content-Type: application/json' -H'X-Requested-With: XMLHttpRequest' -i -XPOST  http://localhost:/search/ -d '{"query":"foo +bar"}'