1 <!--{ 2 "Title": "Writing Web Applications", 3 "Template": true 4 }--> 5 6 <h2>Introduction</h2> 7 8 <p> 9 Covered in this tutorial: 10 </p> 11 <ul> 12 <li>Creating a data structure with load and save methods</li> 13 <li>Using the <code>net/http</code> package to build web applications 14 <li>Using the <code>html/template</code> package to process HTML templates</li> 15 <li>Using the <code>regexp</code> package to validate user input</li> 16 <li>Using closures</li> 17 </ul> 18 19 <p> 20 Assumed knowledge: 21 </p> 22 <ul> 23 <li>Programming experience</li> 24 <li>Understanding of basic web technologies (HTTP, HTML)</li> 25 <li>Some UNIX/DOS command-line knowledge</li> 26 </ul> 27 28 <h2>Getting Started</h2> 29 30 <p> 31 At present, you need to have a FreeBSD, Linux, OS X, or Windows machine to run Go. 32 We will use <code>$</code> to represent the command prompt. 33 </p> 34 35 <p> 36 Install Go (see the <a href="/doc/install">Installation Instructions</a>). 37 </p> 38 39 <p> 40 Make a new directory for this tutorial inside your <code>GOPATH</code> and cd to it: 41 </p> 42 43 <pre> 44 $ mkdir gowiki 45 $ cd gowiki 46 </pre> 47 48 <p> 49 Create a file named <code>wiki.go</code>, open it in your favorite editor, and 50 add the following lines: 51 </p> 52 53 <pre> 54 package main 55 56 import ( 57 "fmt" 58 "io/ioutil" 59 ) 60 </pre> 61 62 <p> 63 We import the <code>fmt</code> and <code>ioutil</code> packages from the Go 64 standard library. Later, as we implement additional functionality, we will 65 add more packages to this <code>import</code> declaration. 66 </p> 67 68 <h2>Data Structures</h2> 69 70 <p> 71 Let's start by defining the data structures. A wiki consists of a series of 72 interconnected pages, each of which has a title and a body (the page content). 73 Here, we define <code>Page</code> as a struct with two fields representing 74 the title and body. 75 </p> 76 77 {{code "doc/articles/wiki/part1.go" `/^type Page/` `/}/`}} 78 79 <p> 80 The type <code>[]byte</code> means "a <code>byte</code> slice". 81 (See <a href="/doc/articles/slices_usage_and_internals.html">Slices: usage and 82 internals</a> for more on slices.) 83 The <code>Body</code> element is a <code>[]byte</code> rather than 84 <code>string</code> because that is the type expected by the <code>io</code> 85 libraries we will use, as you'll see below. 86 </p> 87 88 <p> 89 The <code>Page</code> struct describes how page data will be stored in memory. 90 But what about persistent storage? We can address that by creating a 91 <code>save</code> method on <code>Page</code>: 92 </p> 93 94 {{code "doc/articles/wiki/part1.go" `/^func.*Page.*save/` `/}/`}} 95 96 <p> 97 This method's signature reads: "This is a method named <code>save</code> that 98 takes as its receiver <code>p</code>, a pointer to <code>Page</code> . It takes 99 no parameters, and returns a value of type <code>error</code>." 100 </p> 101 102 <p> 103 This method will save the <code>Page</code>'s <code>Body</code> to a text 104 file. For simplicity, we will use the <code>Title</code> as the file name. 105 </p> 106 107 <p> 108 The <code>save</code> method returns an <code>error</code> value because 109 that is the return type of <code>WriteFile</code> (a standard library function 110 that writes a byte slice to a file). The <code>save</code> method returns the 111 error value, to let the application handle it should anything go wrong while 112 writing the file. If all goes well, <code>Page.save()</code> will return 113 <code>nil</code> (the zero-value for pointers, interfaces, and some other 114 types). 115 </p> 116 117 <p> 118 The octal integer literal <code>0600</code>, passed as the third parameter to 119 <code>WriteFile</code>, indicates that the file should be created with 120 read-write permissions for the current user only. (See the Unix man page 121 <code>open(2)</code> for details.) 122 </p> 123 124 <p> 125 In addition to saving pages, we will want to load pages, too: 126 </p> 127 128 {{code "doc/articles/wiki/part1-noerror.go" `/^func loadPage/` `/^}/`}} 129 130 <p> 131 The function <code>loadPage</code> constructs the file name from the title 132 parameter, reads the file's contents into a new variable <code>body</code>, and 133 returns a pointer to a <code>Page</code> literal constructed with the proper 134 title and body values. 135 </p> 136 137 <p> 138 Functions can return multiple values. The standard library function 139 <code>io.ReadFile</code> returns <code>[]byte</code> and <code>error</code>. 140 In <code>loadPage</code>, error isn't being handled yet; the "blank identifier" 141 represented by the underscore (<code>_</code>) symbol is used to throw away the 142 error return value (in essence, assigning the value to nothing). 143 </p> 144 145 <p> 146 But what happens if <code>ReadFile</code> encounters an error? For example, 147 the file might not exist. We should not ignore such errors. Let's modify the 148 function to return <code>*Page</code> and <code>error</code>. 149 </p> 150 151 {{code "doc/articles/wiki/part1.go" `/^func loadPage/` `/^}/`}} 152 153 <p> 154 Callers of this function can now check the second parameter; if it is 155 <code>nil</code> then it has successfully loaded a Page. If not, it will be an 156 <code>error</code> that can be handled by the caller (see the 157 <a href="/ref/spec#Errors">language specification</a> for details). 158 </p> 159 160 <p> 161 At this point we have a simple data structure and the ability to save to and 162 load from a file. Let's write a <code>main</code> function to test what we've 163 written: 164 </p> 165 166 {{code "doc/articles/wiki/part1.go" `/^func main/` `/^}/`}} 167 168 <p> 169 After compiling and executing this code, a file named <code>TestPage.txt</code> 170 would be created, containing the contents of <code>p1</code>. The file would 171 then be read into the struct <code>p2</code>, and its <code>Body</code> element 172 printed to the screen. 173 </p> 174 175 <p> 176 You can compile and run the program like this: 177 </p> 178 179 <pre> 180 $ go build wiki.go 181 $ ./wiki 182 This is a sample Page. 183 </pre> 184 185 <p> 186 (If you're using Windows you must type "<code>wiki</code>" without the 187 "<code>./</code>" to run the program.) 188 </p> 189 190 <p> 191 <a href="part1.go">Click here to view the code we've written so far.</a> 192 </p> 193 194 <h2>Introducing the <code>net/http</code> package (an interlude)</h2> 195 196 <p> 197 Here's a full working example of a simple web server: 198 </p> 199 200 {{code "doc/articles/wiki/http-sample.go"}} 201 202 <p> 203 The <code>main</code> function begins with a call to 204 <code>http.HandleFunc</code>, which tells the <code>http</code> package to 205 handle all requests to the web root (<code>"/"</code>) with 206 <code>handler</code>. 207 </p> 208 209 <p> 210 It then calls <code>http.ListenAndServe</code>, specifying that it should 211 listen on port 8080 on any interface (<code>":8080"</code>). (Don't 212 worry about its second parameter, <code>nil</code>, for now.) 213 This function will block until the program is terminated. 214 </p> 215 216 <p> 217 <code>ListenAndServe</code> always returns an error, since it only returns when an 218 unexpected error occurs. 219 In order to log that error we wrap the function call with <code>log.Fatal</code>. 220 </p> 221 222 <p> 223 The function <code>handler</code> is of the type <code>http.HandlerFunc</code>. 224 It takes an <code>http.ResponseWriter</code> and an <code>http.Request</code> as 225 its arguments. 226 </p> 227 228 <p> 229 An <code>http.ResponseWriter</code> value assembles the HTTP server's response; by writing 230 to it, we send data to the HTTP client. 231 </p> 232 233 <p> 234 An <code>http.Request</code> is a data structure that represents the client 235 HTTP request. <code>r.URL.Path</code> is the path component 236 of the request URL. The trailing <code>[1:]</code> means 237 "create a sub-slice of <code>Path</code> from the 1st character to the end." 238 This drops the leading "/" from the path name. 239 </p> 240 241 <p> 242 If you run this program and access the URL: 243 </p> 244 <pre>http://localhost:8080/monkeys</pre> 245 <p> 246 the program would present a page containing: 247 </p> 248 <pre>Hi there, I love monkeys!</pre> 249 250 <h2>Using <code>net/http</code> to serve wiki pages</h2> 251 252 <p> 253 To use the <code>net/http</code> package, it must be imported: 254 </p> 255 256 <pre> 257 import ( 258 "fmt" 259 "io/ioutil" 260 <b>"net/http"</b> 261 ) 262 </pre> 263 264 <p> 265 Let's create a handler, <code>viewHandler</code> that will allow users to 266 view a wiki page. It will handle URLs prefixed with "/view/". 267 </p> 268 269 {{code "doc/articles/wiki/part2.go" `/^func viewHandler/` `/^}/`}} 270 271 <p> 272 First, this function extracts the page title from <code>r.URL.Path</code>, 273 the path component of the request URL. 274 The <code>Path</code> is re-sliced with <code>[len("/view/"):]</code> to drop 275 the leading <code>"/view/"</code> component of the request path. 276 This is because the path will invariably begin with <code>"/view/"</code>, 277 which is not part of the page's title. 278 </p> 279 280 <p> 281 The function then loads the page data, formats the page with a string of simple 282 HTML, and writes it to <code>w</code>, the <code>http.ResponseWriter</code>. 283 </p> 284 285 <p> 286 Again, note the use of <code>_</code> to ignore the <code>error</code> 287 return value from <code>loadPage</code>. This is done here for simplicity 288 and generally considered bad practice. We will attend to this later. 289 </p> 290 291 <p> 292 To use this handler, we rewrite our <code>main</code> function to 293 initialize <code>http</code> using the <code>viewHandler</code> to handle 294 any requests under the path <code>/view/</code>. 295 </p> 296 297 {{code "doc/articles/wiki/part2.go" `/^func main/` `/^}/`}} 298 299 <p> 300 <a href="part2.go">Click here to view the code we've written so far.</a> 301 </p> 302 303 <p> 304 Let's create some page data (as <code>test.txt</code>), compile our code, and 305 try serving a wiki page. 306 </p> 307 308 <p> 309 Open <code>test.txt</code> file in your editor, and save the string "Hello world" (without quotes) 310 in it. 311 </p> 312 313 <pre> 314 $ go build wiki.go 315 $ ./wiki 316 </pre> 317 318 <p> 319 (If you're using Windows you must type "<code>wiki</code>" without the 320 "<code>./</code>" to run the program.) 321 </p> 322 323 <p> 324 With this web server running, a visit to <code><a 325 href="http://localhost:8080/view/test">http://localhost:8080/view/test</a></code> 326 should show a page titled "test" containing the words "Hello world". 327 </p> 328 329 <h2>Editing Pages</h2> 330 331 <p> 332 A wiki is not a wiki without the ability to edit pages. Let's create two new 333 handlers: one named <code>editHandler</code> to display an 'edit page' form, 334 and the other named <code>saveHandler</code> to save the data entered via the 335 form. 336 </p> 337 338 <p> 339 First, we add them to <code>main()</code>: 340 </p> 341 342 {{code "doc/articles/wiki/final-noclosure.go" `/^func main/` `/^}/`}} 343 344 <p> 345 The function <code>editHandler</code> loads the page 346 (or, if it doesn't exist, create an empty <code>Page</code> struct), 347 and displays an HTML form. 348 </p> 349 350 {{code "doc/articles/wiki/notemplate.go" `/^func editHandler/` `/^}/`}} 351 352 <p> 353 This function will work fine, but all that hard-coded HTML is ugly. 354 Of course, there is a better way. 355 </p> 356 357 <h2>The <code>html/template</code> package</h2> 358 359 <p> 360 The <code>html/template</code> package is part of the Go standard library. 361 We can use <code>html/template</code> to keep the HTML in a separate file, 362 allowing us to change the layout of our edit page without modifying the 363 underlying Go code. 364 </p> 365 366 <p> 367 First, we must add <code>html/template</code> to the list of imports. We 368 also won't be using <code>fmt</code> anymore, so we have to remove that. 369 </p> 370 371 <pre> 372 import ( 373 <b>"html/template"</b> 374 "io/ioutil" 375 "net/http" 376 ) 377 </pre> 378 379 <p> 380 Let's create a template file containing the HTML form. 381 Open a new file named <code>edit.html</code>, and add the following lines: 382 </p> 383 384 {{code "doc/articles/wiki/edit.html"}} 385 386 <p> 387 Modify <code>editHandler</code> to use the template, instead of the hard-coded 388 HTML: 389 </p> 390 391 {{code "doc/articles/wiki/final-noerror.go" `/^func editHandler/` `/^}/`}} 392 393 <p> 394 The function <code>template.ParseFiles</code> will read the contents of 395 <code>edit.html</code> and return a <code>*template.Template</code>. 396 </p> 397 398 <p> 399 The method <code>t.Execute</code> executes the template, writing the 400 generated HTML to the <code>http.ResponseWriter</code>. 401 The <code>.Title</code> and <code>.Body</code> dotted identifiers refer to 402 <code>p.Title</code> and <code>p.Body</code>. 403 </p> 404 405 <p> 406 Template directives are enclosed in double curly braces. 407 The <code>printf "%s" .Body</code> instruction is a function call 408 that outputs <code>.Body</code> as a string instead of a stream of bytes, 409 the same as a call to <code>fmt.Printf</code>. 410 The <code>html/template</code> package helps guarantee that only safe and 411 correct-looking HTML is generated by template actions. For instance, it 412 automatically escapes any greater than sign (<code>></code>), replacing it 413 with <code>&gt;</code>, to make sure user data does not corrupt the form 414 HTML. 415 </p> 416 417 <p> 418 Since we're working with templates now, let's create a template for our 419 <code>viewHandler</code> called <code>view.html</code>: 420 </p> 421 422 {{code "doc/articles/wiki/view.html"}} 423 424 <p> 425 Modify <code>viewHandler</code> accordingly: 426 </p> 427 428 {{code "doc/articles/wiki/final-noerror.go" `/^func viewHandler/` `/^}/`}} 429 430 <p> 431 Notice that we've used almost exactly the same templating code in both 432 handlers. Let's remove this duplication by moving the templating code 433 to its own function: 434 </p> 435 436 {{code "doc/articles/wiki/final-template.go" `/^func renderTemplate/` `/^}/`}} 437 438 <p> 439 And modify the handlers to use that function: 440 </p> 441 442 {{code "doc/articles/wiki/final-template.go" `/^func viewHandler/` `/^}/`}} 443 {{code "doc/articles/wiki/final-template.go" `/^func editHandler/` `/^}/`}} 444 445 <p> 446 If we comment out the registration of our unimplemented save handler in 447 <code>main</code>, we can once again build and test our program. 448 <a href="part3.go">Click here to view the code we've written so far.</a> 449 </p> 450 451 <h2>Handling non-existent pages</h2> 452 453 <p> 454 What if you visit <a href="http://localhost:8080/view/APageThatDoesntExist"> 455 <code>/view/APageThatDoesntExist</code></a>? You'll see a page containing 456 HTML. This is because it ignores the error return value from 457 <code>loadPage</code> and continues to try and fill out the template 458 with no data. Instead, if the requested Page doesn't exist, it should 459 redirect the client to the edit Page so the content may be created: 460 </p> 461 462 {{code "doc/articles/wiki/part3-errorhandling.go" `/^func viewHandler/` `/^}/`}} 463 464 <p> 465 The <code>http.Redirect</code> function adds an HTTP status code of 466 <code>http.StatusFound</code> (302) and a <code>Location</code> 467 header to the HTTP response. 468 </p> 469 470 <h2>Saving Pages</h2> 471 472 <p> 473 The function <code>saveHandler</code> will handle the submission of forms 474 located on the edit pages. After uncommenting the related line in 475 <code>main</code>, let's implement the handler: 476 </p> 477 478 {{code "doc/articles/wiki/final-template.go" `/^func saveHandler/` `/^}/`}} 479 480 <p> 481 The page title (provided in the URL) and the form's only field, 482 <code>Body</code>, are stored in a new <code>Page</code>. 483 The <code>save()</code> method is then called to write the data to a file, 484 and the client is redirected to the <code>/view/</code> page. 485 </p> 486 487 <p> 488 The value returned by <code>FormValue</code> is of type <code>string</code>. 489 We must convert that value to <code>[]byte</code> before it will fit into 490 the <code>Page</code> struct. We use <code>[]byte(body)</code> to perform 491 the conversion. 492 </p> 493 494 <h2>Error handling</h2> 495 496 <p> 497 There are several places in our program where errors are being ignored. This 498 is bad practice, not least because when an error does occur the program will 499 have unintended behavior. A better solution is to handle the errors and return 500 an error message to the user. That way if something does go wrong, the server 501 will function exactly how we want and the user can be notified. 502 </p> 503 504 <p> 505 First, let's handle the errors in <code>renderTemplate</code>: 506 </p> 507 508 {{code "doc/articles/wiki/final-parsetemplate.go" `/^func renderTemplate/` `/^}/`}} 509 510 <p> 511 The <code>http.Error</code> function sends a specified HTTP response code 512 (in this case "Internal Server Error") and error message. 513 Already the decision to put this in a separate function is paying off. 514 </p> 515 516 <p> 517 Now let's fix up <code>saveHandler</code>: 518 </p> 519 520 {{code "doc/articles/wiki/part3-errorhandling.go" `/^func saveHandler/` `/^}/`}} 521 522 <p> 523 Any errors that occur during <code>p.save()</code> will be reported 524 to the user. 525 </p> 526 527 <h2>Template caching</h2> 528 529 <p> 530 There is an inefficiency in this code: <code>renderTemplate</code> calls 531 <code>ParseFiles</code> every time a page is rendered. 532 A better approach would be to call <code>ParseFiles</code> once at program 533 initialization, parsing all templates into a single <code>*Template</code>. 534 Then we can use the 535 <a href="/pkg/html/template/#Template.ExecuteTemplate"><code>ExecuteTemplate</code></a> 536 method to render a specific template. 537 </p> 538 539 <p> 540 First we create a global variable named <code>templates</code>, and initialize 541 it with <code>ParseFiles</code>. 542 </p> 543 544 {{code "doc/articles/wiki/final.go" `/var templates/`}} 545 546 <p> 547 The function <code>template.Must</code> is a convenience wrapper that panics 548 when passed a non-nil <code>error</code> value, and otherwise returns the 549 <code>*Template</code> unaltered. A panic is appropriate here; if the templates 550 can't be loaded the only sensible thing to do is exit the program. 551 </p> 552 553 <p> 554 The <code>ParseFiles</code> function takes any number of string arguments that 555 identify our template files, and parses those files into templates that are 556 named after the base file name. If we were to add more templates to our 557 program, we would add their names to the <code>ParseFiles</code> call's 558 arguments. 559 </p> 560 561 <p> 562 We then modify the <code>renderTemplate</code> function to call the 563 <code>templates.ExecuteTemplate</code> method with the name of the appropriate 564 template: 565 </p> 566 567 {{code "doc/articles/wiki/final.go" `/func renderTemplate/` `/^}/`}} 568 569 <p> 570 Note that the template name is the template file name, so we must 571 append <code>".html"</code> to the <code>tmpl</code> argument. 572 </p> 573 574 <h2>Validation</h2> 575 576 <p> 577 As you may have observed, this program has a serious security flaw: a user 578 can supply an arbitrary path to be read/written on the server. To mitigate 579 this, we can write a function to validate the title with a regular expression. 580 </p> 581 582 <p> 583 First, add <code>"regexp"</code> to the <code>import</code> list. 584 Then we can create a global variable to store our validation 585 expression: 586 </p> 587 588 {{code "doc/articles/wiki/final-noclosure.go" `/^var validPath/`}} 589 590 <p> 591 The function <code>regexp.MustCompile</code> will parse and compile the 592 regular expression, and return a <code>regexp.Regexp</code>. 593 <code>MustCompile</code> is distinct from <code>Compile</code> in that it will 594 panic if the expression compilation fails, while <code>Compile</code> returns 595 an <code>error</code> as a second parameter. 596 </p> 597 598 <p> 599 Now, let's write a function that uses the <code>validPath</code> 600 expression to validate path and extract the page title: 601 </p> 602 603 {{code "doc/articles/wiki/final-noclosure.go" `/func getTitle/` `/^}/`}} 604 605 <p> 606 If the title is valid, it will be returned along with a <code>nil</code> 607 error value. If the title is invalid, the function will write a 608 "404 Not Found" error to the HTTP connection, and return an error to the 609 handler. To create a new error, we have to import the <code>errors</code> 610 package. 611 </p> 612 613 <p> 614 Let's put a call to <code>getTitle</code> in each of the handlers: 615 </p> 616 617 {{code "doc/articles/wiki/final-noclosure.go" `/^func viewHandler/` `/^}/`}} 618 {{code "doc/articles/wiki/final-noclosure.go" `/^func editHandler/` `/^}/`}} 619 {{code "doc/articles/wiki/final-noclosure.go" `/^func saveHandler/` `/^}/`}} 620 621 <h2>Introducing Function Literals and Closures</h2> 622 623 <p> 624 Catching the error condition in each handler introduces a lot of repeated code. 625 What if we could wrap each of the handlers in a function that does this 626 validation and error checking? Go's 627 <a href="/ref/spec#Function_literals">function 628 literals</a> provide a powerful means of abstracting functionality 629 that can help us here. 630 </p> 631 632 <p> 633 First, we re-write the function definition of each of the handlers to accept 634 a title string: 635 </p> 636 637 <pre> 638 func viewHandler(w http.ResponseWriter, r *http.Request, title string) 639 func editHandler(w http.ResponseWriter, r *http.Request, title string) 640 func saveHandler(w http.ResponseWriter, r *http.Request, title string) 641 </pre> 642 643 <p> 644 Now let's define a wrapper function that <i>takes a function of the above 645 type</i>, and returns a function of type <code>http.HandlerFunc</code> 646 (suitable to be passed to the function <code>http.HandleFunc</code>): 647 </p> 648 649 <pre> 650 func makeHandler(fn func (http.ResponseWriter, *http.Request, string)) http.HandlerFunc { 651 return func(w http.ResponseWriter, r *http.Request) { 652 // Here we will extract the page title from the Request, 653 // and call the provided handler 'fn' 654 } 655 } 656 </pre> 657 658 <p> 659 The returned function is called a closure because it encloses values defined 660 outside of it. In this case, the variable <code>fn</code> (the single argument 661 to <code>makeHandler</code>) is enclosed by the closure. The variable 662 <code>fn</code> will be one of our save, edit, or view handlers. 663 </p> 664 665 <p> 666 Now we can take the code from <code>getTitle</code> and use it here 667 (with some minor modifications): 668 </p> 669 670 {{code "doc/articles/wiki/final.go" `/func makeHandler/` `/^}/`}} 671 672 <p> 673 The closure returned by <code>makeHandler</code> is a function that takes 674 an <code>http.ResponseWriter</code> and <code>http.Request</code> (in other 675 words, an <code>http.HandlerFunc</code>). 676 The closure extracts the <code>title</code> from the request path, and 677 validates it with the <code>TitleValidator</code> regexp. If the 678 <code>title</code> is invalid, an error will be written to the 679 <code>ResponseWriter</code> using the <code>http.NotFound</code> function. 680 If the <code>title</code> is valid, the enclosed handler function 681 <code>fn</code> will be called with the <code>ResponseWriter</code>, 682 <code>Request</code>, and <code>title</code> as arguments. 683 </p> 684 685 <p> 686 Now we can wrap the handler functions with <code>makeHandler</code> in 687 <code>main</code>, before they are registered with the <code>http</code> 688 package: 689 </p> 690 691 {{code "doc/articles/wiki/final.go" `/func main/` `/^}/`}} 692 693 <p> 694 Finally we remove the calls to <code>getTitle</code> from the handler functions, 695 making them much simpler: 696 </p> 697 698 {{code "doc/articles/wiki/final.go" `/^func viewHandler/` `/^}/`}} 699 {{code "doc/articles/wiki/final.go" `/^func editHandler/` `/^}/`}} 700 {{code "doc/articles/wiki/final.go" `/^func saveHandler/` `/^}/`}} 701 702 <h2>Try it out!</h2> 703 704 <p> 705 <a href="final.go">Click here to view the final code listing.</a> 706 </p> 707 708 <p> 709 Recompile the code, and run the app: 710 </p> 711 712 <pre> 713 $ go build wiki.go 714 $ ./wiki 715 </pre> 716 717 <p> 718 Visiting <a href="http://localhost:8080/view/ANewPage">http://localhost:8080/view/ANewPage</a> 719 should present you with the page edit form. You should then be able to 720 enter some text, click 'Save', and be redirected to the newly created page. 721 </p> 722 723 <h2>Other tasks</h2> 724 725 <p> 726 Here are some simple tasks you might want to tackle on your own: 727 </p> 728 729 <ul> 730 <li>Store templates in <code>tmpl/</code> and page data in <code>data/</code>. 731 <li>Add a handler to make the web root redirect to 732 <code>/view/FrontPage</code>.</li> 733 <li>Spruce up the page templates by making them valid HTML and adding some 734 CSS rules.</li> 735 <li>Implement inter-page linking by converting instances of 736 <code>[PageName]</code> to <br> 737 <code><a href="/view/PageName">PageName</a></code>. 738 (hint: you could use <code>regexp.ReplaceAllFunc</code> to do this) 739 </li> 740 </ul> 741