You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dws/README.md

164 lines
6.8 KiB
Markdown

7 years ago
# dws
dws (desktop web server) is a simple http fileserver app. It can be used to
run an http server on a Mac as a native Mac application with a native Mac UI
using Apple's
[Cocoa](https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/OSX_Technology_Overview/CocoaApplicationLayer/CocoaApplicationLayer.html)
framework. This project functions primarily as an exploration into writing a
Go application with a native UI. The app is very similar to Python's
SimpleHTTP server, but demonstrates how to produce a single statically linked
binary that integrates both the desktop UI and the webserver, running in a
shared memory environment.
## download
Compiled applications will be posted on the [releases](/releases) page on the
project's github. You should be able to download the zip, unzip it, and run
the app inside. I've only tested it on macOS Sierra so far.
## build
Building this project requires that you have compilers for Go, C, and
Objective-C, as well as the necessary header files for Cocoa. In practice,
this only means having a Go compiler and the XCode command-line tools, since
the XCode command-line tools will give you everything you need for developing
Cocoa applications. You do not need to use XCode or Interface builder to
compile or work on this project. Given a Go compiler and clang, the process
for building the executable is as follows:
```
go build
```
The Go tool will invoke clang for you automatically. There are no nib files or
resource files. Be aware that this will produce only a binary file, it will
not produce an [App
bundle](https://developer.apple.com/app-store/app-bundles/). The Go tool is
not aware of the App Bundle structure, it is only responsible for building the
executable.
###building an app bundle
Building an App Bundle is reasonably straightforward. The App Bundle is just a
special folder layout. A Makefile is included in the project to simplify this
task. If you have Make, you can build and assemble a fresh App Bundle as follows:
```
make
```
If you _don't_ have Make, you can recreate the folder structure by hand:
```
dws.app/
└── Contents
├── Info.plist
└── MacOS
└── dws
```
# code walk
dws is both a Go application and a Cocoa application. It is compiled with [the
Go tool](https://golang.org/cmd/go/). Although it may be tempting to speak of
the Go and the Cocoa portions of the project as "the Go application" and "the
Cocoa application", that would be misleading: there is just one application.
The Go portion and the Cocoa portion of the application reside in the same
process, with the same address space.
During compilation, the Go tool invokes clang via cgo, and clang compiles a
mixture of C and Objective-C files into object files, which are linked by the
Go linker. cgo will generate some bridging C code for us that will create
function call bindings to allow Go code to call C code and vice-versa. Go is
able to access C type definitions, including struct definitions, but C is not
able to access Go structs. Go is not aware that it is linking against the
Objective-C runtime. Indeed, Go and Objective-C are incapable of interacting
directly, so there is a thin bridging layer written in C that can connect the
two.
## project structure
The project consists of the following packages:
- `main`: the root of the git project. This package defines the application's
entry point and imports the other packages.
- `bg`: the background package. This package contains the http server
implementation. It is written entirely in Go.
- `events`: defines data types that can be used by the bg and ui packages to
communicate. We define these communication primitives in their own package to
avoid a circular import, which is forbidden in Go.
- `ui`: defines our user interface.
- `ui/cocoa`: contains our Cocoa application. This package is the only package
that uses cgo.
## the entry point
The application's entry point is defined in Go.
[`main.go`](https://github.com/jordanorelli/dws/blob/8dee9e5b564edb92c6cbd10103701495dd33f5d6/main.go)
contains the definition of [the `main`
function](https://github.com/jordanorelli/dws/blob/8dee9e5b564edb92c6cbd10103701495dd33f5d6/main.go#L27):
``` go
func main() {
// ...
}
```
We start by [calling
`runtime.LockOSThread()`](https://github.com/jordanorelli/dws/blob/8dee9e5b564edb92c6cbd10103701495dd33f5d6/main.go#L28)
to lock the `main` function to the main thread of the application. This is
because of a Cocoa requirement: OSX will only send events to an application's
main thread, so we need to make sure that we're launching our Cocoa app from
the main thread.
Next, we initialize our Cocoa app by [calling
`ui.Desktop()`](https://github.com/jordanorelli/dws/blob/8dee9e5b564edb92c6cbd10103701495dd33f5d6/main.go#L31).
Our `Desktop` function is defined [in
`ui/ui_darwin.go`](https://github.com/jordanorelli/dws/blob/8dee9e5b564edb92c6cbd10103701495dd33f5d6/ui/ui_darwin.go#L7),
which is compiled on OSX and OSX alone because the file ends with `_darwin.go`
(more about conditional compilation in Go can be found [here on Dave Cheney's
blog](https://dave.cheney.net/2013/10/12/how-to-use-conditional-compilation-with-the-go-build-tool)).
We could conceivably write a drop-in win32 presentation layer by defining a
conformant `ui.Desktop()` function in `ui_windows.go`, but no such win32
implementation has been written yet.
Our [`ui.Desktop`
definition](https://github.com/jordanorelli/dws/blob/8dee9e5b564edb92c6cbd10103701495dd33f5d6/ui/ui_darwin.go#L7-L9)
is very short:
``` go
func Desktop() UI {
return cocoa.Desktop()
}
```
This is where we first encounter [the `ui/cocoa`
package](https://github.com/jordanorelli/dws/tree/8dee9e5b564edb92c6cbd10103701495dd33f5d6/ui/cocoa).
Since we've imported the `cocoa` package, the Go tool compiles all of the
`.go` files, which is just one file:
[`ui.go`](https://github.com/jordanorelli/dws/blob/8dee9e5b564edb92c6cbd10103701495dd33f5d6/ui/cocoa/ui.go). In this file we see a cgo import statement:
``` go
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Cocoa
#include <stdlib.h>
#include "ui.h"
*/
import "C"
```
This is where the Go tool observes that it needs to use cgo to invoke a C
compiler. The `#cgo CFLAGS` line allows us to pass arguments to our C compiler
(clang). Specifically, we enable Objective-C compilation. `LDFLAGS` allows us
to specify linker flags: this is where we ask the linker to link against the
Cocoa framework. The next two lines are plain C. We could write more C code
here, but I prefer to keep it to header includes and to avoid writing too much
mixed Go and C in the same file. `<stdlib.h>` has to be included to define
`C.free`, which allows us to call C's `free` from Go to release memory
allocated on the C heap. We also include `ui.h`, our header file for our Cocoa
ui. We'll take a look at that in a second.