func (ed *EndpointsServer) addMessageField(fieldName string, value interface{}, params map[string]interface{}) {
	pos := strings.Index(fieldName, ".")
	if pos == -1 {
		params[fieldName] = value
		return
	}
	substr := strings.SplitN(fieldName, ".", 2)
	root, remaining := substr[0], substr[1]
	var subParams map[string]interface{}
	_subParams, ok := params[root]
	if ok {
		subParams, ok = _subParams.(map[string]interface{})
		if !ok {
			log.Printf("Problem accessing sub-params: %#v", _subParams)
		}
	} else {
		subParams = make(map[string]interface{})
		params[root] = subParams
	}
	ed.addMessageField(remaining, value, subParams)
}
Converts a . delimitied field name to a message field in parameters.

This adds the field to the params map, broken out so that message
parameters appear as sub-dicts within the outer param.

For example:

{"a.b.c": ["foo"]}

becomes:

{"a": {"b": {"c": ["foo"]}}}

The params argument is a map holding all the parameters, where the value
is eventually set.
package server
import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/rwl/go-endpoints/endpoints"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"path"
	"strings"
)
const defaultURL = "http://localhost:8080"
const defaultRoot = "/_ah/api/"
const spiRootFormat = "/_ah/spi/%s"
var apiExplorerUrl = "http://apis-explorer.appspot.com/apis-explorer/?base="
type EndpointsServer struct {
	configManager *apiConfigManager
	root string
	url string
}
func NewEndpointsServer(URL *url.URL) *EndpointsServer {
	return newEndpointsServerConfig(newApiConfigManager(), "", URL)
}
func NewEndpointsServerRoot(root string, URL *url.URL) *EndpointsServer {
	return newEndpointsServerConfig(newApiConfigManager(), root, URL)
}
func newEndpointsServer() *EndpointsServer {
	u, _ := url.Parse(defaultURL)
	return newEndpointsServerConfig(newApiConfigManager(), "", u)
}
func newEndpointsServerConfig(configManager *apiConfigManager, root string, u *url.URL) *EndpointsServer {
	if root == "" {
		root = defaultRoot
	}
	s := &EndpointsServer{configManager: configManager, root: root}
	s.SetURL(u)
	return s
}
func (ed *EndpointsServer) HandleHttp(mux *http.ServeMux) {
	if mux == nil {
		mux = http.DefaultServeMux
	}
	r := newRouter()
	r.HandleFunc(path.Join(ed.root, "/explorer"), ed.HandleApiExplorerRequest)
	r.HandleFunc(path.Join(ed.root, "/static"), ed.HandleApiStaticRequest)
	r.HandleFunc("/", ed.ServeHTTP)
	mux.Handle(ed.root, r)
}
func (ed *EndpointsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ar, err := newApiRequest(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	ed.serveHTTP(w, ar)
}
func (ed *EndpointsServer) serveHTTP(w http.ResponseWriter, ar *apiRequest) {
	apiConfigResponse, err := ed.getApiConfigs()
	if err != nil {
		ed.failRequest(w, ar.Request, "BackendService.getApiConfigs error: "+err.Error())
		return
	}
	err = ed.handleApiConfigResponse(apiConfigResponse)
	if err != nil {
		ed.failRequest(w, ar.Request, "BackendService.getApiConfigs handling error: "+err.Error())
		return
	}
	_, err = callSpi(ed, w, ar)
	if err != nil {
		reqErr, ok := err.(requestError)
		if ok {
			ed.handleRequestError(w, ar, reqErr)
			return
		} else {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}
func (ed *EndpointsServer) HandleApiExplorerRequest(w http.ResponseWriter, r *http.Request) {
	b := url.URL{Scheme: "http", Host: r.URL.Host, Path: ed.root}
	baseUrl := b.String()
	redirectUrl := apiExplorerUrl + baseUrl
	sendRedirectResponse(redirectUrl, w, r, nil)
}
func (ed *EndpointsServer) HandleApiStaticRequest(w http.ResponseWriter, r *http.Request) {
	request, err := newApiRequest(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	response, body, err := getStaticFile(request.relativeUrl)
	if err == nil && response.StatusCode == 200 {
		w.Header().Add("Content-Type", response.Header.Get("Content-Type"))
		w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))
		fmt.Fprintf(w, body)
	} else {
		log.Printf("Discovery API proxy failed on %s with %d. Details: %s",
			request.relativeUrl, response.StatusCode, body)
		w.Header().Add("Content-Type", response.Header.Get("Content-Type"))
		w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))
		w.WriteHeader(response.StatusCode)
		fmt.Fprint(w, body)
		return
	}
}
func (ed *EndpointsServer) getApiConfigs() (*http.Response, error) {
	req, err := http.NewRequest("POST",
		ed.url+"/_ah/spi/BackendService.getApiConfigs",
		ioutil.NopCloser(bytes.NewBufferString("{}")))
	if err != nil {
		return nil, err
	}
	req.Header.Add("Content-Type", "application/json")
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	return resp, nil
}
func verifyResponse(response *http.Response, statusCode int, contentType string) error {
	if response.StatusCode != statusCode {
		return fmt.Errorf("HTTP status code does not match the response status code: %d != %d", statusCode, response.StatusCode)
	}
	if len(contentType) == 0 {
		return nil
	}
	ct := response.Header.Get("Content-Type")
	if len(ct) == 0 {
		return errors.New("Response does not specify a Content-Type")
	}
	if ct == contentType {
		return nil
	}
	return fmt.Errorf("Incorrect response Content-Type: %s != %s", ct, contentType)
}
func (ed *EndpointsServer) handleApiConfigResponse(apiConfigResponse *http.Response) error {
	err := verifyResponse(apiConfigResponse, 200, "application/json")
	if err == nil {
		body, err := ioutil.ReadAll(apiConfigResponse.Body)
		defer apiConfigResponse.Body.Close()
		if err != nil {
			return err
		}
		err = ed.configManager.parseApiConfigResponse(string(body))
		if err != nil {
			return err
		}
		return nil
	}
	return err
}
var callSpi = func(ed *EndpointsServer, w http.ResponseWriter, origRequest *apiRequest) (string, error) {
	var methodConfig *endpoints.ApiMethod
	var params map[string]string
	if origRequest.isRpc() {
		methodConfig = ed.lookupRpcMethod(origRequest)
		params = nil
	} else {
		methodConfig, params = ed.lookupRestMethod(origRequest)
	}
	if methodConfig == nil {
		corsHandler := newCheckCorsHeaders(origRequest.Request)
		return sendNotFoundResponse(w, corsHandler), nil
	}
	spiRequest, err := ed.transformRequest(origRequest, params, methodConfig)
	if err != nil {
		return err.Error(), err
	}
	discovery := newDiscoveryService(ed.configManager)
	discoveryResponse, ok := discovery.handleDiscoveryRequest(spiRequest.URL.Path,
		spiRequest, w)
	if ok {
		return discoveryResponse, nil
	}
	url := buildSpiUrl(ed, spiRequest)
	req, err := http.NewRequest("POST", url, spiRequest.Body)
	if err != nil {
		return "", err
	}
	req.Header.Add("Content-Type", "application/json")
	req.RemoteAddr = spiRequest.RemoteAddr
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	return handleSpiResponse(ed, origRequest, spiRequest, resp,
		methodConfig, w)
}
var buildSpiUrl = func(ed *EndpointsServer, spiRequest *apiRequest) string {
	return ed.url + fmt.Sprintf(spiRootFormat, spiRequest.URL.Path)
}
var handleSpiResponse = func(ed *EndpointsServer, origRequest, spiRequest *apiRequest, response *http.Response, methodConfig *endpoints.ApiMethod, w http.ResponseWriter) (string, error) {
	for header, value := range response.Header {
		if header == "Content-Type" && !strings.HasPrefix(value[0], "application/json") {
			respBody, err := ioutil.ReadAll(response.Body)
			if err != nil {
				return "", err
			}
			response.Body.Close()
			return ed.failRequest(w, origRequest.Request,
				fmt.Sprintf("Non-JSON reply: %s", respBody)), nil
		}
	}
	err := ed.checkErrorResponse(response)
	if err != nil {
		return "", err
	}
	respBody, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return "", err
	}
	response.Body.Close()
	var body string
	if origRequest.isRpc() {
		body, err = ed.transformJsonrpcResponse(spiRequest, string(respBody))
	} else {
		emptyResponse := ed.checkEmptyResponse(origRequest, methodConfig, w)
		if emptyResponse {
			return "", nil
		}
		body, err = ed.transformRestResponse(string(respBody))
	}
	if err != nil {
		return body, err
	}
	corsHandler := newCheckCorsHeaders(origRequest.Request)
	corsHandler.updateHeaders(w.Header())
	for k, vals := range response.Header {
		w.Header()[k] = vals
	}
	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))
	w.WriteHeader(response.StatusCode)
	fmt.Fprint(w, body)
	return body, nil
}
func (ed *EndpointsServer) failRequest(w http.ResponseWriter, origRequest *http.Request, message string) string {
	corsHandler := newCheckCorsHeaders(origRequest)
	return sendErrorResponse(message, w, corsHandler)
}
func (ed *EndpointsServer) lookupRestMethod(origRequest *apiRequest) (*endpoints.ApiMethod, map[string]string) {
	methodName, method, params := ed.configManager.lookupRestMethod(origRequest.URL.Path, origRequest.Method)
	origRequest.Method = methodName
	return method, params
}
func (ed *EndpointsServer) lookupRpcMethod(origRequest *apiRequest) *endpoints.ApiMethod {
	if origRequest.bodyJson == nil {
		return nil
	}
	methodName, ok := origRequest.bodyJson["method"]
	methodNameStr, ok2 := methodName.(string)
	if !ok || !ok2 {
		methodNameStr = ""
	}
	version, ok := origRequest.bodyJson["apiVersion"]
	versionStr, ok3 := version.(string)
	if !ok || !ok3 {
		versionStr = ""
	}
	origRequest.Method = methodNameStr
	return ed.configManager.lookupRpcMethod(methodNameStr, versionStr)
}
func (ed *EndpointsServer) transformRequest(origRequest *apiRequest, params map[string]string, methodConfig *endpoints.ApiMethod) (*apiRequest, error) {
	var request *apiRequest
	var err error
	if origRequest.isRpc() {
		request, err = ed.transformJsonrpcRequest(origRequest)
	} else {
		methodParams := methodConfig.Request.Params
		request, err = ed.transformRestRequest(origRequest, params, methodParams)
	}
	if err != nil {
		return request, err
	}
	request.URL.Path = methodConfig.RosyMethod
	return request, nil
}