view controllers/externalwfs.go @ 342:c6bd6ed18942

Use INSTEAD OF trigger for user deletion As we already have this for updating, it's more symmetric to make the whole thing look like a real table. TODO: make it happen also for user creation.
author Tom Gottfried <tom@intevation.de>
date Mon, 06 Aug 2018 12:37:06 +0200
parents bd292a554b6e
children e98033e3683a
line wrap: on
line source

package controllers

import (
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"

	"github.com/gorilla/mux"
	"golang.org/x/net/html/charset"

	"gemma.intevation.de/gemma/config"
)

func externalWFSProxy(rw http.ResponseWriter, req *http.Request) {

	external := config.ExternalWFSs()
	if external == nil || len(external) == 0 {
		http.NotFound(rw, req)
		return
	}
	wfs := mux.Vars(req)["wfs"]

	alias, found := external[wfs]
	if !found {
		http.NotFound(rw, req)
		return
	}
	data, ok := alias.(map[string]interface{})
	if !ok {
		log.Printf("error: badly configured external wfs %s\n", wfs)
		http.Error(rw,
			http.StatusText(http.StatusInternalServerError),
			http.StatusInternalServerError)
		return
	}

	urlS, found := data["url"]
	if !found {
		log.Printf("error: missinf url fore xternal wfs %s\n", wfs)
		http.Error(rw,
			http.StatusText(http.StatusInternalServerError),
			http.StatusInternalServerError)
		return
	}

	prefix, ok := urlS.(string)
	if !ok {
		log.Printf("error: badly configured url for external wfs %s\n", wfs)
		http.Error(rw,
			http.StatusText(http.StatusInternalServerError),
			http.StatusInternalServerError)
		return
	}

	log.Printf("%v\n", prefix)
	url := prefix + "?" + req.URL.RawQuery

	remoteReq, err := http.NewRequest(req.Method, url, req.Body)
	if err != nil {
		http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusBadRequest)
		return
	}

	client := &http.Client{}
	resp, err := client.Do(remoteReq)
	if err != nil {
		http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusBadRequest)
		return
	}

	var transcode bool

	for k := range resp.Header {
		v := resp.Header.Get(k)
		rw.Header().Set(k, v)
		if strings.TrimSpace(strings.ToLower(k)) == "content-type" &&
			strings.Contains(strings.ToLower(v), "application/xml") {
			transcode = true
		}
	}

	defer resp.Body.Close()

	if transcode {
		to := useHTTPS(req) + "://" + req.Host
		if !strings.HasPrefix(req.URL.Path, "/") {
			to += "/"
		}
		to += req.URL.Path
		log.Printf("rewrite %s to: %s\n", prefix, to)
		err = rewrite(rw, resp.Body, prefix, to)
	} else {
		_, err = io.Copy(rw, resp.Body)
	}

	if err != nil {
		log.Printf("copy error: %v\n", err)
	}
}

func rewrite(w io.Writer, r io.Reader, from, to string) error {

	decoder := xml.NewDecoder(r)
	decoder.CharsetReader = charset.NewReaderLabel

	encoder := xml.NewEncoder(w)

	replace := func(s string) string {
		return strings.Replace(s, from, to, -1)
	}

	var ns nsdef

tokens:
	for {
		tok, err := decoder.Token()
		switch {
		case tok == nil && err == io.EOF:
			break tokens
		case err != nil:
			return err
		}

		switch t := tok.(type) {
		case xml.StartElement:
			ns = ns.push()
			t = t.Copy()

			attr := make([]xml.Attr, len(t.Attr))

			//var lns string
			for i, at := range t.Attr {
				switch {
				case at.Name.Space == "xmlns":
					ns.define(at.Value, at.Name.Local)
					attr[i] = xml.Attr{Name: at.Name, Value: at.Value}
				default:
					attr[i] = xml.Attr{Name: at.Name, Value: replace(at.Value)}
				}
			}
			if s := ns.lookup(t.Name.Space); s != "" {
				t.Name.Space = ""
				t.Name.Local = s + ":" + t.Name.Local
			}
			t.Attr = attr
			tok = t

		case xml.CharData:
			tok = xml.CharData(replace(string(t)))

		case xml.EndElement:
			//log.Printf("lookup %s -> %s\n", t.Name.Space, ns.lookup(t.Name.Space))
			if s := ns.lookup(t.Name.Space); s != "" {
				t.Name.Space = ""
				t.Name.Local = s + ":" + t.Name.Local
				tok = t
			}
			ns = ns.pop()
		}
		if err := encoder.EncodeToken(tok); err != nil {
			return err
		}
	}

	return encoder.Flush()
}

type nsdef []map[string]string

func (n nsdef) lookup(ns string) string {
	for i := len(n) - 1; i >= 0; i-- {
		if s := n[i][ns]; s != "" {
			return s
		}
	}
	return ""
}

func (n nsdef) push() nsdef {
	return append(n, make(map[string]string))
}

func (n nsdef) pop() nsdef {
	if l := len(n); l > 0 {
		n[l-1] = nil
		n = n[:l-1]
	}
	return n
}

func (n nsdef) define(ns, s string) {
	if n != nil {
		n[len(n)-1][ns] = s
	}
}