Go: Encoding Maps with Non-String Keys to JSON

I ran into some issues with 1.6.2 encoding a map to a JSON structure because it had int/int64 keys. Sure, JSON prescribes string-only keys, but I incorrectly made the reasonable assumption that Go would coerce these to strings. It turns out that this change was made in the very recent past and that, as of the last release (1.7.1) (and probably earlier ones) this should no longer be a problem.

Unfortunately, AppEngine still runs on 1.6.2, for now. So, if you had the same problem, you will have to go the conventional routeĀ and translate these keys to strings, yourself, prior to marshaling.

Advertisements

Using the Google Maps Client Library for Go in AppEngine

The default HTTP transport implementation for Go isn’t supported when running in AppEngine. Trying to use it will result in the following error:

http.DefaultTransport and http.DefaultClient are not available in App Engine. See https://cloud.google.com/appengine/docs/go/urlfetch/

To fix this, you need to use the http.Client implementation from AppEngine’s urlfetch package (imported from google.golang.org/appengine/urlfetch).

uc := urlfetch.Client(ctx)

options := []maps.ClientOption {
    maps.WithHTTPClient(uc),
    maps.WithAPIKey(GoogleApiKey),
}

c, err := maps.NewClient(options...)
if err != nil {
    panic(err)
}

nsr := &maps.NearbySearchRequest{
    Location: &maps.LatLng {
        Lat: latitude,
        Lng: longitude,
    },
    Radius: radius,
    OpenNow: true,
    RankBy: maps.RankByProminence,
    Type: maps.PlaceTypeRestaurant,
}

psr, err := c.NearbySearch(ctx, nsr)
if err != nil {
    panic(err)
}

Implementing Sessions Under AppEngine With Go

A simple and intuitive package named cascadestore provided by the go-appengine-sessioncascade project to implement and combine Memcache, Datastore, the request context, or any combination of them, as session backends under AppEngine.

Example:

package handlers

import (
    "net/http"

    "google.golang.org/appengine"
    "google.golang.org/appengine/log"

    "github.com/dsoprea/goappenginesessioncascade"
)

const (
    sessionName = "MainSession"
)

var (
    sessionSecret = []byte("SessionSecret")
    sessionStore  = cascadestore.NewCascadeStore(cascadestore.DistributedBackends, sessionSecret)
)

func HandleRequest(w http.ResponseWriter, r *http.Request) {
    ctx := appengine.NewContext(r)

    if session, err := sessionStore.Get(r, sessionName); err != nil {
        panic(err)
    } else {
        if vRaw, found := session.Values["ExistingKey"]; found == false {
            log.Debugf(ctx, "Existing value not found.")
        } else {
            v := vRaw.(string)
            log.Debugf(ctx, "Existing value: [%s]", v)
        }

        session.Values["NewKey"] = "NewValue"
        if err := session.Save(r, w); err != nil {
         panic(err)
        }
    }
}

AppEngine Development Environment Module Restrictions

AppEngine has some very tight but obvious restrictions on what types of Python modules can be invoked from application code. The general rule of thumb is that modules that need filesystem access or C code can’t be used. So, which modules are allowed or disallowed? Which modules are partially implemented, or defined and completely empty (yes, there are/were some)?

Unfortunately, the only official list of such modules is very dated.

There was a point, in the not-too-distant past, that the reigning perception of AppEngine’s module support was that the development environment does no such restriction, leaving a dangerous and scary gap between what will definitely run on your system and what you can be sure will run in production.

It turns out that there is some protection in the development environment.. Maybe even complete protection.

The google/appengine/tools/devappserver2/python/sandbox.py module appears to be wholly responsible for the loading of modules. At the top, there’s a sys.meta_path assignment. This is what appears as of version 1.8.4:

  sys.meta_path = [
      StubModuleImportHook(),
      ModuleOverrideImportHook(_MODULE_OVERRIDE_POLICIES),
      BuiltinImportHook(),
      CModuleImportHook(enabled_library_regexes),
      path_override_hook,
      PyCryptoRandomImportHook,
      PathRestrictingImportHook(enabled_library_regexes)
      ]

This defines a series of module “finders” responsible for resolving imported modules. This is where restrictions are imposed. The following are descriptions/insights about each one.

StubModuleImportHook: Replaces complete modules with different ones.
ModuleOverrideImportHook: Adjust partially white-listed modules (symbols may be added, removed, or updated).
BuiltinImportHook: Imposes a white-list on builtin modules. This raises an ImportError on everything else.
CModuleImportHook: Imposes a white-list on C modules.
path_override_hook: Has an instance of PathOverrideImportHook. It looks like this module looks for modules in special paths (the kind scattered in the.
PyCryptoRandomImportHook: Fixes the loading of Crypto.Random.OSRNG.new .
PathRestrictingImportHook: Makes sure any remaining imports come out of an accessible path.

If you have a question of what specific modules are involved, look in the sandbox.py module mentioned above. The first four finders are relatively concrete. Most of their modules are expressed in lists.