Tuesday, March 29, 2016

Using Dart (dartlang) as an HTML client and Go (golang) as a server

Hi all! I'm back again. This blog might be short on explanation, but basically it gives an example of mixing Google technologies. What this example does is serve MP3 files from the server to the client, which get played on the client's speakers.

First, the server. For that, you'll need Go (https://golang.org/) and a folder to put the Go code and the MP3 files. The only Go file you need is main.go, as follows:

package main      

import (
    "log"
    "net/http"
    "strings"
//    "io/ioutil"
)

func main() {
    port := "localhost:8000"
    log.Println("Opening port", port)
    http.HandleFunc("/music/", getMusic)
    http.ListenAndServe(port, nil)
    }
    


func getMusic(w http.ResponseWriter, r *http.Request)  {
    w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
    w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE")
    url := r.URL.Path
    urlParts := strings.Split(url, "/")
    log.Println(urlParts[2])
    fileName := urlParts[2] + ".mp3"
    log.Println("Sending file", fileName)
    http.ServeFile(w, r, fileName)
      
}

What this code does is listen for a REST "get" request for "localhost:8000/music/". The ending "/" is needed so that the sender can hitch the file name to the URL. The code attaches CORS headers to the response, as this is a cross-origin situation. The URL gets parsed, then the resulting file name gets ".mp3" attached. That file then gets send back to the client with the headers.

The client is a bit more complex. For the client, you need a pubspec.yaml file, a /web subfolder, and Dart (https://www.dartlang.org/).

Your pubspec.yaml should look like:

name: DartMusicPlayer2
author: Ross Albertson <magnusthegood@yahoo.com>
description: A Dart music player using classes

dependencies:
  browser: any
  dart_to_js_script_rewriter: any
  
transformers:
- dart_to_js_script_rewriter   

Use any project name and description you wish, and replace the author with your name and e-mail address.  You'll need the dependencies and transformers to translate your Dart and HTML correctly.

The HTML (/web/index.html) is pretty trivial:

<!DOCTYPE html>
<html>
    <head>
        <title>Dart/Go Music Player 2</title>
        <script type="application/dart" src="main.dart"></script>
        <script src="packages/browser/dart.js"></script>
    </head>
    <body>
        <p>"July" by Weekend <button id="july">Play/Stop</button></p>
        <p>"Drive Blind" by Ride <button id="drive-blind">Play/Stop</button></p>
    </body>
</html> 

The <button> id's and the plain text can be substituted by any mp3 song you have. Lastly, there is the /web/main.dart, as follows:

import 'dart:html';
import 'dart:web_audio';

AudioContext audioContext;

void main() {
  audioContext = new AudioContext();
  new Song(querySelector("#july") as ButtonElement, "July");
  new Song(querySelector("#drive-blind") as ButtonElement, "Drive_Blind");
}

class Song {
  ButtonElement button;
  bool _playing = false;
  // AudioContext _audioContext;
  AudioBufferSourceNode _source;
  String title;

  Song(this.button, this.title) {
    // _audioContext = new AudioContext();

    button..onClick.listen((e) => _toggle());
  }

  _toggle() {
    _playing = !_playing;
    _playing ? _start() : _stop();
  }

  _start() {
    return HttpRequest
        .request("http://localhost:8000/music/$title",
            responseType: "arraybuffer")
        .then((HttpRequest httpRequest) {
      return audioContext
          .decodeAudioData(httpRequest.response)
          .then((AudioBuffer buffer) {
        _source = audioContext.createBufferSource();
        _source.buffer = buffer;
        _source.connectNode(audioContext.destination);
        _source.start(0);
      });
    });
  }

  _stop() {
    _source.stop(0);
  }
}

A global AudioContext is needed in any event. The Song class just requires a <button> element and a file name, minus the file extension. If you want to use multiple file formats, just change the Go so that is uses the URL variable as is, and send the whole file name to the Song constructor. I combined several online Dart examples to come up with this code. The "decodeAudioData" is needed to make the client work with more browsers. This object-oriented approach allows you to put your songs in separate threads. It is also needed to be able to play music after stopping a song; I discovered that quirk in an earlier attempt. You might need to compile the Dart in "debug" mode (pub build --mode debug) and use those /build/web files in production. I found this necessary when using IIS. One last thing... make sure your Dart HTTP request matches with your Go URL and port number.



Thursday, September 4, 2014

Example of Beegae - Part 3

Now we're finally getting to the code. This app maintains a list of names, so naturally I made a struct called "Name". Here is models/model.go:

  // model
package models

import (
    "appengine"
    "appengine/datastore"
)

type Name struct {
    FirstName, LastName string
}

const COLLECTION = "name"

func (this Name) String() string {
    return this.FirstName + " " + this.LastName
}

func (name Name) GetMatchCount(c appengine.Context) (count int, err error) {
    //    c := appengine.NewContext(rq)
    q := datastore.NewQuery(COLLECTION).Filter("FirstName =", name.FirstName).
        Filter("LastName =", name.LastName)
    var results []Name
    _, err = q.GetAll(c, &results)
    if err != nil {
        count = -1
    } else {
        count = len(results)
    }
    return
}

func (this Name) Add(c appengine.Context) error {
    key := datastore.NewIncompleteKey(c, COLLECTION, nil)
    _, err := datastore.Put(c, key, &this)
    return err
}

func GetAllNames(c appengine.Context) (names []Name, err error) {
    q := datastore.NewQuery(COLLECTION)
    _, err = q.GetAll(c, &names)
    return
}

As you can see, I'm using Datastore. The COLLECTION constant sets the data type string. The GetMatchCount function queries the database for names matching the incoming name, then calculates the number of matches and returns the total. The GetAll API function retrieves all of the documents matching the query. NewIncompleteKey generates a document key. Lastly, the String function tells Go how to convert a Name to a string. We will see how that is used soon. The Contexts determine which database gets accessed. 

Now we'll get to the controllers. Here is controllers/default.go:

package controllers
import (
    "fmt"
    "github.com/astaxie/beegae"
    "log"
    "models"
)

type MainController struct {
    beegae.Controller
}

func (this *MainController) Get() {
    this.Data["Title"] = "Beegae Example #1"
    this.TplNames = "form.tpl"
}

type VerifyController MainController

func (this *VerifyController) Post() {
    context := this.AppEngineCtx
    first := this.GetString("first")
    last := this.GetString("last")
    name := models.Name{first, last}
    count, err := name.GetMatchCount(context)
    message := fmt.Sprintf("%s %d", name, count)
    if err != nil {
        message = err.Error()
    }
    if count == 0 {
        if err := name.Add(context); err != nil {
            message = err.Error()
        }
    }
    this.Data["Title"] = "Beegae Example #1"
    this.Data["Message"] = message
    this.TplNames = "verify.tpl"
}

type ListController struct {
    beegae.Controller
}

func (this *ListController) Get() {
    context := this.AppEngineCtx
    //    var names []models.Name
    names, err := models.GetAllNames(context)
    if err != nil {
        log.Fatal(err.Error())\
    }
    this.Data["Title"] = "Beegae Example #1"
    this.Data["Names"] = names
    this.TplNames = "list.tpl"
}

GetString retrieves the form variable with the given name and casts it as a string. As you can see, the Sprintf function refers to a variable of type "Name" as a string. models.String tells Go how to implicitly cast that structure. In Beegae, constructors are actually types, which are used as receivers for the appropriate REST function. 
The views are pretty much trivial except for list.tpl. Here that is:

  
<!DOCTYPE html>

<html>
      <head>
        <title>{{.Title}}</title>
        <link rel="stylesheet" href="/static/css/style.css" type="text/css" />

    </head>
    <body>
        <h3>Updated List</h3>
        <ul>
            {{range $v := .Names}}
                <li>{{$v}}</li>
            {{end}}
        </ul>
    </body>
</html>        

The "range" keyword loops through the list of names and assigns each entry in turn to $v. As you can see, the data is treated like a string. The routing is fairly simple:

package routers
import (
    "beegaenames/controllers"
    "github.com/astaxie/beegae"
)

func init() {
    beegae.Router("/", &controllers.MainController{})
    beegae.Router("/verify", &controllers.VerifyController{})
    beegae.Router("/list", &controllers.ListController{})
}

That is routers/myrouter.go. Each Router has a URL and a variable. As you saw in earlier code, those variables are usually structs that contain a member of type beegae.Controller. The last thing we'll look at is how I deployed this app.

Friday, August 29, 2014

Example of Beegae - Part 2

The next step was to establish and configure my project. Before we get into that, I wanted a couple of Go packages to make things easier, namely github.com/astaxie/beego and github.com/beego/bee. I compiled the second package into the "bee" command.
I already had a directory in my root called "goprojects2". I created a "src" subdirectory. Then I went into the SDK command prompt and issued:

set gopath=%gopath%;c:\goprojects2

then went to the goprpjects2 directory and entered

bee new beegaenames

The "bee" command created a scaffold for my project. However, I had to convert the project from Beego to Beegae. I went into main.go (in goprojects2/src/beegaenames), and replaced "beego" with "beegae" .  I also went into controllers/default.go and routers/router.go, replacing "beego" with "beegae".
I needed to do one last thing before writing Go, namely create index.yaml on the same level as main.go. This file indexed my database. Here are the contents:

indexes:

- kind: Name
  ancestor: no
  properties:
  - name: FirstName
    direction: asc
  - name: LastName
    direction: asc

In the next installment, we'll get into the actual code.

Tuesday, August 26, 2014

Example of Beegae (Beego for App Engine) - Part 1

Sorry I've been gone for so long! I recently created a simple web form using Beegae. The setup was rather involved, but worth it. I will assume you have Git and Python 2.x already installed. I needed to install Go (http://golang.org/) and Google Cloud SDK (https://cloud.google.com/developers/ ; click "Download the SDK" for instructions). I used Datastore, the original NoSQL native to App Engine.

The next thing I had to do was install the Go SDK. To to that, I went into the Google Cloud SDK console window, and issued

gcloud components update gae-go

If you're using the Windows version, there might be a bug in goapp.bat. You will probably find it at

C:\Program Files\Google\Cloud SDK\google-cloud-sdk\platform\google_appengine

I issued the following to retrieve the Beegae Go package:

goapp get github.com/astaxie/beegae

and got this error...

'C:\Program' is not recognized as an internal or external command,
operable program or batch file.

I fixed the problem by these steps:

  1. Open Notepad as Administrator
  2. Open goapp.bat
  3. Put the %GOROOT%\bin\%EXENAME% line, along with all the parameters, on a new line
  4. Put quotation marks (") around %GOROOT%\bin\%EXENAME%
  5. Save (make sure NOT as a .TXT file) and Exit
If you're a bit squeamish, Copy goapp.bat as goapp.bat.old first. I reissued the above "goapp" command, and this time it ran. In the next installment, I'll get into how I configured this project.



Monday, February 10, 2014

After a long "vacation"...

Hi all!

I'm sorry I left you all in the lurch. I have been busy writing code, and you can see some of it at https://github.com/ozonesurfer . As a warning, Tiedot is a work in progress, and if you try to use my current code with Tiedot 2.0, it won't work.

That being said, I thought I would put my  2 cents in on some of the current "wars of religion". Like Tiedot, the tech world is in transition. Transitions are messy things; you have to phase the old out gracefully, and allow the new to mature. People are ditching their landlines for cell phones. People now text instead of call, and e-mail/post instead of writing letters. I'm not going to say one set of technology is better than others. They're just different, each with their benefits and flaws.

As for what I've seen, one of my friends bought a Chromebook and a Windows Phone, and she's perfectly happy with both. Another friend feels incapable of using Windows. She has an old iMac, and plans to eventually get a new one.

 As for me, I bought this Vista HP laptop in 2009; now I use Windows 8.1 on the same machine. My only real problem is my graphics chips sometimes struggle with video. And yes, I once used Windows 7 too. The only real complaint I have with Windows 8.x is the lack of built-in instructions. Despite that shortcoming, I leveraged my knowledge of previous versions and my brains to figure out how to use it. I demoed this knowledge to a couple friends, and not only did they love what they saw, one experimented in front of me and we learned more features together... another later bought a Windows 8 phone.

I consider Windows 8.x as a product of the transition from computers as they were once known to tablets/smartphones. You can't please everyone, but programmers (like me) are stuck with having to try. Some people think change is bad (or at least too inconvenient), and others think change doesn't happen fast enough. I just work with what's available, and I try to keep an open mind to the new.

I think that's embodied in my current software stack: Windows, document-based NoSQL, and Go. I can hear you say "Whrere is the web server? What? No Apache?".  As for Apache or IIS, with Go I don't really need them. The Go framework comes with the "net/http" package, which has the ListenAndServe function. I can specify any web address/port I have permission to manipulate as a parameter.

I'm not saying LAMP is bad or useless. If you know what you're doing, it gets the job done. And yes, I've used JavaScript (namely jQuery) in Go websites. And with Gorilla's websockets, I can broadcast user input in a chatroom website, or even manipulate my websites using the command line. If anyone knows how to do the latter using something else, I'd love to read that code. However, the command line will have to be read, and the entered data be used by the website,while the website is running...no cheating! E-mail it to me at ross.albertson@yahoo.com.      

I haven't seen screenshots of Windows 8.1 Update, but I'm not freaking out about it. I figured out how to use 8.0 and 8.1, and with God's help I'll learn the next version. And I'm not married to Go. I plan to learn Java 8 (or try to) fairly soon. If you stop learning, you stop growing as a person. Besides, to me a technological occupation means signing up to be a professional student to some degree. One last thing... one's belief in a higher power is sacred, but no technology is... if something you created can't be changed, it's broken.

Tuesday, September 24, 2013

Go (golang), MVC and MongoDB, part 2

Now comes the fun part, the actual code. The first thing we need to do is configure the website. That consists of routes.go and config.go, both in src/gomgoweb. First we'll tackle the routes. Here is that file in full:
routes.go
// routes
package gomgoweb

import (
// "fmt"
"github.com/QLeelulu/goku"
)

// routes
var Routes []*goku.Route = []*goku.Route{
// static file route
&goku.Route{
Name:     "static",
IsStatic: true,
Pattern:  "/static/(.*)",
},
// default controller and action route
&goku.Route{
Name:    "default",
Pattern: "/{controller}/{action}/{id}",
Default: map[string]string{"controller": "home", "action": "index",
"id": "0"},
},
}

This file configures the two kinds of web addresses we'll be using. The "static" route is for things like JPEG files,or, in our case, a CSS file. The "default" route is for the MVC web addresses. As you probably guessed, the Default dictionary determines the home address of the website (/home/index).

Noe for the more difficult part, config.go. This contains the domain name of the website, as well as the site's behavior and global constants. Here is the whole thing:

config.go
// config
package gomgoweb

import (
"github.com/QLeelulu/goku"
"path"
"runtime"
"time"
)

var Config *goku.ServerConfig = &goku.ServerConfig{
Addr:           "localhost:8080",
ReadTimeout:    10 * time.Second,
WriteTimeout:   10 * time.Second,
MaxHeaderBytes: 1 << 20,
StaticPath:     "static",
ViewPath:       "views",
LogLevel:       goku.LOG_LEVEL_LOG,
Debug:          true,
}

const (
DATABASE     = "gomgoweb-8"
BAND_COL     = "bands"
LOCATION_COL = "locations"
GENRE_COL    = "genres"
ALBUM_COL    = "albums"
DATABASE_DSN = "localhost"
)

func init() {
// project root directory
_, filename, _, _ := runtime.Caller(1)
Config.RootDir = path.Dir(filename)
}

You could use just about any string for the database name. "COL" here is short for "collection". In MongoDB, data is first organized into databases, then into collections. "Addr" determines the domain name. In a real life website, this would more likely be a publicly accessed web address. This file also tells Goku where to find the view HTML files, as well as gives a potential alias for the the "static" directory.  

We of course need a main package and entry point. Those are provided by gomgoweb.go, which goes in the project's root directory. Here it is:

gomgoweb.go
// gomgoweb
package main

import (
// "fmt"
"github.com/QLeelulu/goku"
"gomgoweb"
"gomgoweb/controllers"
// "gomgoweb/models"
// "labix.org/v2/mgo/bson"
"log"
)

func main() {
// fmt.Println("Hello World!")
rt := &goku.RouteTable{Routes: gomgoweb.Routes}
middlewares := []goku.Middlewarer{}
s := goku.CreateServer(rt, middlewares, gomgoweb.Config)
goku.Logger().Logln("Server start on", s.Addr)
log.Fatal(s.ListenAndServe())
}

var home = controllers.HomeController
var band = controllers.BandController
var album = controllers.AlbumController

You can safely ignore the commented-out lines. As you can see, the main() function creates the web server in memory and connects it to the files we previously created. You also see that this file establishes the controllers as global variables. The names of those variables can be anything that's legal in Go. I did things this way for the sake of simple explanation. You could have created those variables in the controller files, given them empty ("_") names, and imported the controller package as _ "gomgoweb/controllers", but I think the way I coded it is more intuitive. The ListenAndServe() method is what actually starts the web server up.

At this point, if you left out all mention of controllers, this code would run. However, you wouldn't be able to view the results in your browser. For that, you need a controller and HTML template file. We'll create those soon. In the next installment, we'll get into other global aspects of this project: the CSS and the layout template.  

Monday, September 23, 2013

Go (golang) and MongoDB Example 2 - A Series on Goku (MVC) Websites

Sorry I've been gone for so long. I decided to discontinue the Couchbase series in favor of MongoDB. I'm going to assume you have a working copy of Go and MongoDB. If you don't, you can get those at http://golang.org/ and http://www.mongodb.org/ . You will also need http://bazaar.canonical.com/en/ and Git (http://git-scm.com/ or https://github.com/) to install the necessary Go packages. You can retrieve those packages by issuing the following commands:

go get github.com/QLeelulu/goku

go get labix.org/v2/mgo

You will also need the /data/db path (C:\data\db in Windows) in your root for MongoDB to work.

This website/web server does not need IIS or Apache to run. It basically catalogs a CD collection. I used the LiteIDE (https://code.google.com/p/golangide/downloads/list) environment to write this program. Zeus (http://www.zeusedit.com/go.html) is another possibility, although you have to pay for it after 45 days.

Another piece of housekeeping is the directory tree. You should create a "gomgoweb" directory on your machine, and you'll need the following paths under it (use "\" instead of "/" if you're using Windows):

src/gomgoweb
src/gomgoweb/controllers
src/gomgoweb/models
src/gomgoweb/views/home
src/gomgoweb/views/band
src/gomgoweb/views/album
src/gomgoweb/views/shared
src/gomgoweb/static

If you have any experience with the MVC architecture, this tree should look familiar. Go expects to see all packages other than "main" under a src/<package_name> directory. The last thing you need to do (if they haven't been done for you) is set up the "gopath" environment variable. That should contain a path to your default Go package directory and the root path of this project. In Windows/DOS, that would look like

set gopath=C:\Go\pkg;C:\gomgoweb

Well, you get the idea. In the next installment, we'll get our hands dirty on some code.