Web: Zinc, Teapot, Seaside & JSON
Zinc (by Sven Van Caekenberghe) is Pharo's built-in HTTP client and server.
37.1 Zinc HTTP Components
Zinc (by Sven Van Caekenberghe) is Pharo's built-in HTTP client and server.
For routing, write a delegate object implementing handleRequest:, or use Teapot.
"Client"
ZnClient new get: 'https://pharo.org'. "→ the body as a String"
'https://pharo.org' asUrl retrieveContents.
ZnEasy get: 'https://pharo.org'. "→ a ZnResponse"
ZnClient new
url: 'https://api.github.com/repos/pharo-project/pharo';
accept: ZnMimeType applicationJson;
contentReader: [ :entity | STONJSON fromString: entity contents ];
get. "→ a Dictionary"
ZnClient new
url: 'https://httpbin.org/post';
entity: (ZnEntity with: '{"amount": 42}' type: ZnMimeType applicationJson);
post;
response."Server"
(ZnServer startDefaultOn: 8080)
onRequestRespond: [ :request |
ZnResponse ok: (ZnEntity text: 'Hello from Zinc, you asked for ' , request uri printString) ].
"Visit http://localhost:8080/anything"
ZnServer stopDefault.37.2 Teapot: micro web framework
Teapot stopAll stops all servers. Route patterns support typed parameters (<id:IsInteger>) and regexes.
"Load: Metacello new baseline: 'Teapot'; repository: 'github://zeroflag/Teapot:master/source'; load."
| accounts |
accounts := Dictionary new.
(Teapot configure: { #port -> 8080 })
GET: '/' -> 'Bank API';
GET: '/hello/<name>' -> [ :req | 'Hello, ' , (req at: #name) ];
GET: '/accounts' -> [ :req | accounts keys asArray ]; output: #json;
GET: '/accounts/<owner>' -> [ :req |
| acct |
acct := accounts at: (req at: #owner).
{ #owner -> acct owner . #balance -> acct balance } asDictionary ]; output: #json;
POST: '/accounts/<owner>' -> [ :req |
accounts at: (req at: #owner) put: (BankAccount owner: (req at: #owner)).
TeaResponse created ];
exception: KeyNotFound -> (TeaResponse notFound body: 'no such account');
start.curl -X POST http://localhost:8080/accounts/ada
curl http://localhost:8080/accounts/ada # {"owner":"ada","balance":0}37.3 JSON and STON
STON (Smalltalk Object Notation) is a JSON superset that serializes arbitrary object graphs, including class names and shared references:
STON is used by Iceberg/Tonel metadata, smalltalkCI configs, and settings files.
"STONJSON (built in)"
STONJSON fromString: '{"name": "Ada", "langs": ["Smalltalk", "Lisp"]}'. "→ a Dictionary"
STONJSON toString: { 'name' -> 'Ada' . 'age' -> 36 } asDictionary.
STONJSON toStringPretty: #(1 2 3).
"NeoJSON (load from github://svenvc/NeoJSON)"
NeoJSONReader fromString: '[1, 2, {"a": true}]'.
NeoJSONWriter toString: #(1 2 3).
NeoJSONWriter toStringPretty: (Dictionary new at: #x put: 1; yourself).
"Mapping JSON to domain objects with NeoJSON"
(NeoJSONReader on: '{"owner": "Ada", "balance": 100}' readStream)
mapInstVarsFor: BankAccount;
nextAs: BankAccount. "→ a BankAccount(Ada: 100)"STON toString: (BankAccount owner: 'Ada' balance: 100).
"→ 'BankAccount{#owner:''Ada'',#balance:100}'"
STON fromString: 'BankAccount{#owner:''Ada'',#balance:100}'.
STON toStringPretty: (1 to: 3) asOrderedCollection.37.4 Seaside: component-based web apps
Seaside builds web applications from stateful components with callbacks instead of URLs and templates; its control flow uses call:/answer: (implemented with continuations via thisContext).
Forms and flows:
Seaside also offers a canvas API for every HTML tag, jQuery/Ajax integration, REST support (WARestfulHandler), and runs on Pharo, Squeak, GemStone (the classic "GLASS" stack), and VA Smalltalk.
"Load: Metacello new baseline: 'Seaside3'; repository: 'github://SeasideSt/Seaside:master/repository'; load."
WAComponent << #CounterComponent
slots: { #count };
package: 'Seaside-Demo'
CounterComponent >> initialize
super initialize.
count := 0
CounterComponent >> renderContentOn: html
html heading: count printString.
html anchor
callback: [ count := count + 1 ];
with: '++'.
html space.
html anchor
callback: [ count := count - 1 ];
with: '--'
CounterComponent class >> canBeRoot
^ trueWAAdmin register: CounterComponent asApplicationAt: 'counter'.
ZnZincServerAdaptor startOn: 8080.
"Visit http://localhost:8080/counter"NameEditor >> renderContentOn: html
html form: [
html textInput value: name; callback: [ :value | name := value ].
html submitButton callback: [ self answer: name ]; with: 'Save' ]
"In a parent component's callback: show the editor and wait for its answer"
name := self call: NameEditor new.37.5 Other web options
Zinc WebSockets (ZnWebSocket), Zinc REST (ZnRestServer).
Tealight, Ston-based REST frameworks; PharoJS (compile Smalltalk to JavaScript for the browser); Amber Smalltalk (Smalltalk on JavaScript).
Mustache templates, Pillar/Microdown for documentation (Pharo's own docs and books use Microdown).