1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
|
title: References in Ponzu
References in Ponzu allow you to create relationships between your Content types.
Ponzu uses an embedded database, rather than a more traditional relational database
with SQL support. This may seem unnatural since there is no native concept of
"foreign keys" or "joins" like you may be used to. Instead, Ponzu wires up your
data using references, which are simply URL paths, like `/api/content?type=Post&id=1`
A foreign key as a URL path?! Am I crazy? No! For the purpose Ponzu serves,
this structure works quite well, especially given its creation was specifically
tuned for HTTP/2 features such as "Request/Response Multiplexing" and "Server Push."
There is a deeper dive into the HTTP/2 concepts [below](/References/Overview/#designed-for-http2), but first we'll walk through
a quick tutorial on Ponzu's references.
To generate references from the CLI, please [read through the documentation](/CLI/Generating-References).
The example below assumes you understand the syntax.
---
### Create Your Content Types
Here we are creating two Content types, `Author` and `Book`. A `Book` will keep
a reference to an `Author` in the sense that an author wrote the book.
```bash
$ ponzu gen c author name:string photo:string:file bio:string:textarea
$ ponzu gen c book title:string author:@author,name pages:int year:int
```
The structs generated for each look like:
`content/author.go`
```go
type Author struct {
item.Item
Name string `json:"name"`
Photo string `json:"photo"`
Bio string `json:"bio"`
}
```
`content/book.go`
```go
type Book struct {
item.Item
Title string `json:"title"`
Author string `json:"author"`
Pages int `json:"pages"`
Year int `json:"year"`
}
```
Notice how the `Author` field within the `Book` struct is a `string` type, not
an `Author` type. This is because the `Author` is stored as a `string` in our
database, as a reference to the `Author`, instead of embedding the `Author` data
inside the `Book`.
Some example JSON data for the two structs looks like:
<kbd>GET</kbd> `/api/content?type=Author&id=1` (`Author`)
```json
{
"data": [
{
"uuid": "024a5797-e064-4ee0-abe3-415cb6d3ed18",
"id": 1,
"slug": "item-id-024a5797-e064-4ee0-abe3-415cb6d3ed18",
"timestamp": 1493926453826,
"updated": 1493926453826,
"name": "Shel Silverstein",
"photo": "/api/uploads/2017/05/shel-silverstein.jpg",
"bio": "Sheldon Allan Silverstein was an American poet..."
}
]
}
```
<kbd>GET</kbd> `/api/content?type=Book&id=1` (`Book`)
```json
{
"data": [
{
"uuid": "024a5797-e064-4ee0-abe3-415cb6d3ed18",
"id": 1,
"slug": "item-id-024a5797-e064-4ee0-abe3-415cb6d3ed18",
"timestamp": 1493926453826,
"updated": 1493926453826,
"title": "The Giving Tree",
"author": "/api/content?type=Author&id=1",
"pages": 57,
"year": 1964
}
]
}
```
As you can see, the `Author` is a reference as the `author` field in the JSON
response for a `Book`. When you're building your client, you need to make a second
request for the `Author`, to the URL path found in the `author` field of the `Book`
response.
For example, in pseudo-code:
```bash
# Request 1:
$book = GET /api/content?type=Book&id=1
# Request 2:
$author = GET $book.author # where author = /api/content?type=Author&id=1
```
Until recently, this would be considered bad practice and would be costly to do
over HTTP. However, with the wide availability of HTTP/2 clients, including all
modern web browsers, mobile devices, and HTTP/2 libraries in practically every
programming language, this pattern is fast and scalable.
---
### Designed For HTTP/2
At this point, you've likely noticed that you're still making two independent
HTTP requests to your Ponzu server. Further, if there are multiple references or more
than one item, you'll be making many requests -- _how can that be efficient?_
There are two main concepts at play: Request/Response Multiplexing and Server Push.
#### Request/Response Multiplexing
With HTTP/2, a client and server (peers) transfer data over a single TCP connection,
and can send data back and forth at the same time. No longer does a request need
to wait to be sent until after an expected response is read. This means that HTTP
requests can be sent much faster and at the _same time_ on a single connection.
Where previously, a client would open up several TCP connections, the re-use of a
single connection reduces CPU overhead and makes the server more efficient.
This feature is automatically provided to you when using HTTP/2 - the only
requirement is that you connect via HTTPS and have active TLS certificates, which
you can get for free by running Ponzu with the `--https` flag and configuring it
with a properly set, active domain name of your own.
#### Server Push
Another impactful feature of HTTP/2 is "Server Push": the ability to preemptively
send a response from the server to a client without waiting for a request. This
is where Ponzu's reference design really shows it's power. Let's revisit the
example from above:
```bash
# Request 1:
$book = GET /api/content?type=Book&id=1
# Request 2:
$author = GET $book.author # where author = /api/content?type=Author&id=1
```
Instead of waiting for the server to respond with the data for `$book.author`,
the response data is already in the client's cache before we even make the request!
Now there is no round-trip made to the server and back, and the client reads the
pushed response from cache in fractions of a millisecond.
But, how does the server know which response to push and when? You'll need to
specify which fields of the type you've requested should be pushed. This is done
by implementing the [`item.Pushable` interface](/Interfaces/Item#itempushable).
See the example below which demonstrates a complete implementation on the `Book`
struct, which has a reference to an `Author`.
##### Example
`content/book.go`
```go
...
type Book struct {
item.Item
Title string `json:"title"`
Author string `json:"author"`
Pages int `json:"pages"`
Year int `json:"year"`
}
func (b *Book) Push(res http.ResponseWriter, req *http.Request) ([]string, error) {
return []string{
// the json struct tag is used to tell the server which
// field(s) it should push - only URL paths originating
// from your server can be pushed!
"author",
}, nil
}
...
```
Now, whenever a single `Book` is requested, the server will preemptively push the
`Author` referenced by the book. The response for the `Author` will _already be
on the client_ and will remain there until a request for the referenced `Author`
has been made.
!!! note "What else can I Push?"
Only fields that are URL paths originating from your server can be pushed.
This means that you could also implement `item.Pushable` on the `Author`
type, and return `[]string{"photo"}, nil` to push the Author's image!
---
### Other Considerations
HTTP/2 Server Push is a powerful feature, but it can be abused just like anything
else. To try and help mitigate potential issues, Ponzu has put some "stop-gaps"
in place. Server Push is only activated on **single item** API responses, so you
shouldn't expect to see references or files pushed from the `/api/contents` endpoint.
An exception to this is the `/api/search` endpoint, which only the **first**
result is pushed (if applicable) no matter how many items are in the response.
You should take advantage of HTTP/2 in Ponzu and get the most out of the system.
With the automatic HTTPS feature, there is no reason not to and you gain the
additional benefit of encrypting your traffic - which your users will appreciate!
|