diff --git a/README.md b/README.md index 4f3cc02c..ac82c383 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # Multiple Language Versions -* [English](en/) -* [French](fr/) -* [Spanish](es/) -* [中文](zh/) -* [日本語](ja/) -* [Turkish](tr/) -* [Português - Brasil](pt-br/) -* [German](de/) -* [Русский](ru/) +* [English](en/preface.md) +* [French](fr/preface.md) +* [Spanish](es/preface.md) +* [中文](zh/preface.md) +* [日本語](ja/preface.md) +* [Turkish](tr/preface.md) +* [Português - Brasil](pt-br/preface.md) +* [German](de/preface.md) +* [Русский](ru/preface.md) # Donate diff --git a/de/02.2.md b/de/02.2.md index 4a640fc4..9a9d870c 100644 --- a/de/02.2.md +++ b/de/02.2.md @@ -219,7 +219,7 @@ Gruppierter Ansatz. prefix string ) -Wird innerhalb von `constant()` einer Konstanten das Schlüsselwort `iota` als Wert zugewiesen, hat sie den Wert `0`. Werden den folgenden Konstanten keinee expliziten Werte zugewiesen, wird der letzte zugeweise Wert von `iota` um 1 erhöht und und der folgenden Konstante zugewiesen. Dieses Verhalten beleuchten wir im folgenden Absatz. +Wird innerhalb von `constant()` einer Konstanten das Schlüsselwort `iota` als Wert zugewiesen, hat sie den Wert `0`. Werden den folgenden Konstanten keine expliziten Werte zugewiesen, wird der letzte zugeweise Wert von `iota` um 1 erhöht und der folgenden Konstante zugewiesen. Dieses Verhalten beleuchten wir im folgenden Absatz. ### Aufzählen mit iota diff --git a/de/02.3.md b/de/02.3.md index 564031a5..be0d87ae 100644 --- a/de/02.3.md +++ b/de/02.3.md @@ -333,7 +333,7 @@ Nun können wir den Wert von `x` in der Funktion ändern. Aber warum nutzen wir ### defer -Go besitzt mit `defer` ein weiteres nützliches Schlüsselwort. Du kannst `defer` mehrmals in einer Funktion nutzen. Sie werden in umgekehrter Reihenfolge am Ende einer Funktion ausgeführt. Im dem Fall, dass Dein Programm eine Datei öffnet, muss diese erst wieder geschlossen werden, bevor Fehler zurückgeben werden können. Schauen wir uns ein paar Beispiele an. +Go besitzt mit `defer` ein weiteres nützliches Schlüsselwort. Du kannst `defer` mehrmals in einer Funktion nutzen. Sie werden in umgekehrter Reihenfolge am Ende einer Funktion ausgeführt. Im Fall, dass Dein Programm eine Datei öffnet, muss diese erst wieder geschlossen werden, bevor Fehler zurückgeben werden können. Schauen wir uns ein paar Beispiele an. func LesenSchreiben() bool { file.Open("Datei") @@ -520,4 +520,4 @@ Es gibt spezielle Operatoren beim Importieren von Paketen, die Anfänger oftmals - [Inhaltsverzeichnis](preface.md) - Vorheriger Abschnitt: [Grundlagen von Go](02.2.md) -- Nächster Abschnitt: [Struct](02.4.md) \ No newline at end of file +- Nächster Abschnitt: [Struct](02.4.md) diff --git a/de/02.4.md b/de/02.4.md index 98d7dc3c..592d8a6c 100644 --- a/de/02.4.md +++ b/de/02.4.md @@ -179,7 +179,7 @@ In Go können alle Datenttypen eingebettet werden. fmt.Println("Ihre bevorzugte Nummer lautet", jane.int) } -Im oberen Beispiel ist erkenntlich, dass alle Datentypen eingebettet werden und Funktion auf ihre Werte zugreifen können. +Im oberen Beispiel ist erkenntlich, dass alle Datentypen eingebettet werden und Funktionen auf ihre Werte zugreifen können. Aber es gibt noch ein kleines Problem. Was geschieht, wenn Mensch die Eigenschaft `telefon` besitzt und Student eine Eigenschaft mit dem gleichen Namen besitzt? @@ -211,4 +211,4 @@ Go nutzt einen einfachen Weg zur Unterscheidung. Um die Eigenschaft `telefon` vo - [Inhaltsverzeichnis](preface.md) - Vorheriger Abschnitt: [Kontrollstrukturen und Funktionen](02.3.md) -- Nächster Abschnitt: [Objektorientiertes Programmieren](02.5.md) \ No newline at end of file +- Nächster Abschnitt: [Objektorientiertes Programmieren](02.5.md) diff --git a/de/02.5.md b/de/02.5.md index 613d52ab..745bdc25 100644 --- a/de/02.5.md +++ b/de/02.5.md @@ -1,6 +1,6 @@ # 2.5 Objektorientierte Programmierung -In den letzen beiden Abschnitten hatten wir uns mit Funktionen und Structs beschäftigt, aber hast Du jemals daran gedacht, Funktionen als Eigenschaft in einem Struct zu verwenden? In diesem Abschnitt werden ich Dir eine besondere Art von Funktionen vorstellen, die einen Reciever (engl. to recieve - empfangen) besitzen. Sie werden auch `Methoden` genannt. +In den letzen beiden Abschnitten hatten wir uns mit Funktionen und Structs beschäftigt, aber hast Du jemals daran gedacht, Funktionen als Eigenschaft in einem Struct zu verwenden? In diesem Abschnitt werde ich Dir eine besondere Art von Funktionen vorstellen, die einen Reciever (engl. to recieve - empfangen) besitzen. Sie werden auch `Methoden` genannt. ## Methoden @@ -306,4 +306,4 @@ Nun bist Du bereit, Dein eigenes, objektorientiers Programm zu schreiben. Auch M - [Inhaltsverzeichnis](preface.md) - Vorheriger Abschnitt: [Struct](02.4.md) -- Nächster Abschnitt: [Interface](02.6.md) \ No newline at end of file +- Nächster Abschnitt: [Interface](02.6.md) diff --git a/de/02.6.md b/de/02.6.md index f36c2528..be3f18de 100644 --- a/de/02.6.md +++ b/de/02.6.md @@ -89,7 +89,7 @@ Zudem implementiert jeder Datentyp das leere Interface `interface{}`, da es kein ### Interface als Datentyp -Welche Arten von Werten können mit einem Interface verknüpft werden? Wen wir eine Variable vom Typ Interface definieren, dann kann jeder Datentyp, der das Interface implementiert wird, der Variable zugewiesen werden. +Welche Arten von Werten können mit einem Interface verknüpft werden? Wen wir eine Variable vom Typ Interface definieren, dann kann jeder Datentyp, der das Interface implementiert, der Variable zugewiesen werden. Es ist wie im oberen Beispiel. Erstellen wir eine Variable "m" mit dem Interface Männer, kann jeder Student, Mensch oder Mitarbeiter "m" zugewiesen werden. So könnten wir ein Slice mit dem Interface Männer jeden Datentyp hinzufügen, der ebenfalls das Interface Männer implementiert. Bedenke aber, dass sich das Verhalten von Slices ändert, wenn dies Elemente eines Interface statt eines Datentypes verwendet. diff --git a/de/02.8.md b/de/02.8.md index 197e4d92..ad7784ba 100644 --- a/de/02.8.md +++ b/de/02.8.md @@ -23,10 +23,10 @@ In diesem Kapitel haben wir uns hauptsächlich mit den 25 Schlüsselwörtern in - `map` definiert eine Map, welche Hashtabellen in anderen Programmiersprachen ähneln. - `range` wird genutzt, um Daten aus einem `slice`, einer `map` oder einem`channel` zu erhalten. -Wenn Du verstanden was, wie die 25 Schlüsselwörter einzusetzen sind, dann hast Du bereits eine Menge über Go gelernt. +Wenn Du verstanden hast, wie die 25 Schlüsselwörter einzusetzen sind, dann hast Du bereits eine Menge über Go gelernt. ## Links - [Inhaltsverzeichnis](preface.md) - Vorheriger Abschnitt: [Nebenläufigkeit](02.7.md) -- Nächstes Kapitel: [Grundlagen des Internets](03.0.md) \ No newline at end of file +- Nächstes Kapitel: [Grundlagen des Internets](03.0.md) diff --git a/de/05.5.md b/de/05.5.md index 87bdeca8..de0c65c8 100644 --- a/de/05.5.md +++ b/de/05.5.md @@ -9,17 +9,21 @@ beedb is an open source project that supports basic ORM functionality, but doesn Because beedb supports `database/sql` interface standards, any driver that implements this interface can be used with beedb. I've tested the following drivers: -Mysql: [github.com/ziutek/mymysql/godrv](github.com/ziutek/mymysql/godrv) +Mysql: [github/go-mysql-driver/mysql](https://github.com/go-sql-driver/mysql) -Mysql: [code.google.com/p/go-mysql-driver](code.google.com/p/go-mysql-driver) +PostgreSQL: [github.com/bmizerany/pq](https://github.com/lib/pq) -PostgreSQL: [github.com/bmizerany/pq](github.com/bmizerany/pq) +SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) -SQLite: [github.com/mattn/go-sqlite3](github.com/mattn/go-sqlite3) +Mysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql) -MS ADODB: [github.com/mattn/go-adodb](github.com/mattn/go-adodb) +MsSql: [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) -ODBC: [bitbucket.org/miquella/mgodbc](bitbucket.org/miquella/mgodbc) +MS ADODB: [github.com/mattn/go-adodb](https://github.com/mattn/go-adodb) + +Oracle: [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) + +ODBC: [bitbucket.org/miquella/mgodbc](https://bitbucket.org/miquella/mgodbc) ## Installation diff --git a/en/01.4.md b/en/01.4.md index 57fd9123..1d21d0a0 100644 --- a/en/01.4.md +++ b/en/01.4.md @@ -91,7 +91,7 @@ LiteIDE features. ## Sublime Text -Here I'm going to introduce you the Sublime Text 2 (Sublime for short) + GoSublime + gocode + MarGo. Let me explain why. +Here I'm going to introduce you the Sublime Text 3 (Sublime for short) + GoSublime + gocode. Let me explain why. - Intelligent completion @@ -112,7 +112,16 @@ First, download the version of [Sublime](http://www.sublimetext.com/) suitable f 1. Press ``Ctrl+` ``, open the command tool and input the following commands. - import urllib2,os; pf='Package Control.sublime-package'; ipp=sublime.installed_packages_path(); os.makedirs(ipp) if not os.path.exists(ipp) else None; urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler())); open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()); print 'Please restart Sublime Text to finish installation' + Applicable to Sublime Text 3: + +```Go + import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()) +``` + Applicable to Sublime Text 2: + +```Go + import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read());print('Please restart Sublime Text to finish installation') +``` Restart Sublime Text when the installation has finished. You should then find a `Package Control` option in the "Preferences" menu. @@ -399,6 +408,20 @@ This is an awesome text editor released as open source cross platform my Microso It works with Windows, Mac, Linux. It has go package built, it provides code linting. +## Atom + +Atom is an awesome text editor released as open source cross platform, built on Electron , and based on everything we love about our favorite editors. We designed it to be deeply customizable, but still approachable using the default configuration. + +Download: https://atom.io/ + +##Goglang + +Gogland is the codename for a new commercial IDE by JetBrains aimed at providing an ergonomic environment for Go development. + +The official version is not yet released。 + +Download:https://www.jetbrains.com/go/ + ## Links - [Directory](preface.md) diff --git a/en/01.5.md b/en/01.5.md index 6df9c420..8bb67ba1 100644 --- a/en/01.5.md +++ b/en/01.5.md @@ -1,6 +1,6 @@ # 1.5 Summary -In this chapter, we talked about how to install Go using three different methods including from source code, the standard package and via third-party tools. Then we showed you how to configure the Go development environment, mainly covering how to setup your `$GOPATH`. After that, we introduced some steps for compiling and deploying Go programs. We then covered Go commands, including the compile, install, format and test commands. Finally, there are many powerful tools to develop Go programs such as LiteIDE, Sublime Text, Vim, Emacs, Eclipse, IntelliJ IDEA, etc. You can choose any one you like exploring the world of Go. +In this chapter, we talked about how to install Go using three different methods including from source code, the standard package and via third-party tools. Then we showed you how to configure the Go development environment, mainly covering how to setup your `$GOPATH`. After that, we introduced some steps for compiling and deploying Go programs. We then covered Go commands, including the compile, install, format and test commands. Finally, there are many powerful tools to develop Go programs such as LiteIDE, Sublime Text, VSCode, Atom, Goglang, Vim, Emacs, Eclipse, IntelliJ IDEA, etc. You can choose any one you like exploring the world of Go. ## Links diff --git a/en/02.1.md b/en/02.1.md index fb21f6ed..bc8b1775 100644 --- a/en/02.1.md +++ b/en/02.1.md @@ -17,7 +17,7 @@ While this might seem to be a shallow problem at the top, but when the codebase For other languages there are many variables when it comes to writing code, every language is good for its use case, but Go is a little special in that turf because it was designed at a company which is the very synonym of the Internet (and distributed computing), typically the flow of writing code goes from Python to Java to C++ for optimization purposes, but the problem is that almost all languages which are widely in use right now were written decades ago when 1GB storage came at a much higher price compared to now, where storage and computing has gotten cheap. Computers are getting multiples cores these days and the "old languages" don't harness concurrency in a way that go does, not because those languages are bad, but simply because that usecase wasn't relevant when the languages evolved. -So to mitigate all the problems that Google faced with the current tools, they wrote a systems language called Go, which you are about to learn! There are many many advantages to using golang, and there might be disadvantages too for every coin has both sides. But significant improvements in places like code formatting, since they designed the language in such a way that there won't be wars on how to format code, the gocode written by anyone in the world (assuming they know and use `gofmt`) will look exactly the same, this won't seem to matter until you work in a team! also when the company uses automated code review or some other fancy technique then in other languages which don't have strict and standard formatting rules then the code might get screwed up, but not in go! +So to mitigate all the problems that Google faced with the current tools, they wrote a systems language called Go, which you are about to learn! There are many advantages to using golang, and there might be disadvantages too for every coin has both sides. But significant improvements in places like code formatting, since they designed the language in such a way that there won't be wars on how to format code, the gocode written by anyone in the world (assuming they know and use `gofmt`) will look exactly the same, this won't seem to matter until you work in a team! also when the company uses automated code review or some other fancy technique then in other languages which don't have strict and standard formatting rules then the code might get screwed up, but not in go! Go was designed with concurrency in mind, please note that parallelism != concurrency, there is an amazing post by Rob Pike on the golang blog, blog.golang.org, you will find it there, it is worth a read. diff --git a/en/05.5.md b/en/05.5.md index 8076f84a..0079e0fd 100644 --- a/en/05.5.md +++ b/en/05.5.md @@ -9,17 +9,19 @@ beedb is an open source project that supports basic ORM functionality, but doesn Because beedb supports `database/sql` interface standards, any driver that implements this interface can be used with beedb. I've tested the following drivers: -Mysql: [github.com/ziutek/mymysql/godrv](github.com/ziutek/mymysql/godrv) +Mysql: [github/go-mysql-driver/mysql](https://github.com/go-sql-driver/mysql) -Mysql: [code.google.com/p/go-mysql-driver](code.google.com/p/go-mysql-driver) +PostgreSQL: [github.com/bmizerany/pq](https://github.com/lib/pq) -PostgreSQL: [github.com/bmizerany/pq](github.com/bmizerany/pq) +SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) -SQLite: [github.com/mattn/go-sqlite3](github.com/mattn/go-sqlite3) +Mysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql) -MS ADODB: [github.com/mattn/go-adodb](github.com/mattn/go-adodb) +MS ADODB: [github.com/mattn/go-adodb](https://github.com/mattn/go-adodb) -ODBC: [bitbucket.org/miquella/mgodbc](bitbucket.org/miquella/mgodbc) +Oracle: [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) + +ODBC: [bitbucket.org/miquella/mgodbc](https://bitbucket.org/miquella/mgodbc) ## Installation diff --git a/en/05.6.md b/en/05.6.md index a787410b..08b0d2c7 100644 --- a/en/05.6.md +++ b/en/05.6.md @@ -9,7 +9,7 @@ As the C language of the 21st century, Go has good support for NoSQL databases, redis is a key-value storage system like Memcached, that supports the string, list, set and zset(ordered set) value types. There are some Go database drivers for redis: - +- [https://github.com/garyburd/redigo](https://github.com/garyburd/redigo) - [https://github.com/alphazero/Go-Redis](https://github.com/alphazero/Go-Redis) - [http://code.google.com/p/tideland-rdc/](http://code.google.com/p/tideland-rdc/) - [https://github.com/simonz05/godis](https://github.com/simonz05/godis) diff --git a/en/06.4.md b/en/06.4.md index 658f37c1..48740086 100644 --- a/en/06.4.md +++ b/en/06.4.md @@ -49,7 +49,7 @@ Refresh the page and you'll see the following: Figure 6.7 hijacking the session has succeeded. -Here we see that we can hijack sessions between different browsers, and actions performed in one one browser can affect the state of a page in another browser. Because HTTP is stateless, there is no way of knowing that the session id from firefox is simulated, and chrome is also not able to know that it's session id has been hijacked. +Here we see that we can hijack sessions between different browsers, and actions performed in one browser can affect the state of a page in another browser. Because HTTP is stateless, there is no way of knowing that the session id from firefox is simulated, and chrome is also not able to know that it's session id has been hijacked. ## prevent session hijacking diff --git a/en/09.1.md b/en/09.1.md index 8f4bc738..1301535c 100644 --- a/en/09.1.md +++ b/en/09.1.md @@ -21,7 +21,7 @@ As can be seen from the figure, to complete a CSRF attack, the victim must compl -1. Log into trusted site A, and store a local Cookie. -2. Without going through existing site A, access the dangerous link to site B. -As a reader you may be asking: "If I do not meet the above two conditions, I will will not be subjected to CSRF attacks." Yes this is true, however you cannot guarantee that the following does not occur: +As a reader you may be asking: "If I do not meet the above two conditions, I will not be subjected to CSRF attacks." Yes this is true, however you cannot guarantee that the following does not occur: - You cannot guarantee that when you are logged into a site, the site didn't launch any hidden tabs. - You cannot guarantee that when you close your browser, your cookies will immediately expire and your last session will have ended. @@ -37,7 +37,7 @@ You might be a little scared after reading the section above. But fear is a good Preventative measures against CSRF attacks can be taken on both the server and client sides of a web application. However, CSRF attacks are most effectively thwarted on the server side. -There are many ways of preventing CSRF attacks on the server side. Most approaches stem from from the following two aspects: +There are many ways of preventing CSRF attacks on the server side. Most approaches stem from the following two aspects: 1. Maintaining proper use of GET, POST and cookies. 2. Including a pseudo-random number with non-GET requests. diff --git a/en/09.4.md b/en/09.4.md index d8823175..d6bbe9bd 100644 --- a/en/09.4.md +++ b/en/09.4.md @@ -60,7 +60,7 @@ SQL injection attacks can be devastating -how can do we even begin to defend aga 1. Strictly limit permissions for database operations so that users only have the minimum set of permissions required to accomplish their work, thus minimizing the risk of database injection attacks. 2. Check that input data has the expected data format, and strictly limit the types of variables that can be submitted. This can involve regexp matching, or using the strconv package to convert strings into other basic types for sanitization and evaluation. 3. Transcode or escape from pairs of special characters ( '"\&*; etc. ) before persisting them into the database. Go's `text/template` package has a `HTMLEscapeString` function that can be used to return escaped HTML. -4. Use your database's parameterized query interface. Parameterized statements use parameters instead of concatenating user input variables in embedded SQL statements; in other words, they do not directly splice ​​SQL statements. For example, using the the `Prepare` function in Go's `database/sql` package, we can create prepared statements for later execution with `Query` or `Exec(query string, args... interface {})`. +4. Use your database's parameterized query interface. Parameterized statements use parameters instead of concatenating user input variables in embedded SQL statements; in other words, they do not directly splice ​​SQL statements. For example, using the `Prepare` function in Go's `database/sql` package, we can create prepared statements for later execution with `Query` or `Exec(query string, args... interface {})`. 5. Before releasing your application, thoroughly test it using professional tools for detecting SQL injection vulnerabilities and to repair them, if they exist. There are many online open source tools that do just this, such as sqlmap, SQLninja, to name a few. 6. Avoid printing out SQL error information on public webpages. Attackers can use these error messages to carry out SQL injection attacks. Examples of such errors are type errors, fields not matching errors, or any errors containing SQL statements. diff --git a/en/12.4.md b/en/12.4.md index 327cc6ab..6babaf26 100644 --- a/en/12.4.md +++ b/en/12.4.md @@ -1,6 +1,6 @@ # 12.4 Backup and recovery -In this section, we'll discuss another aspect of application management: data backup and recovery on production servers. We often encounter situations where production servers don't behave as as we expect them to. Server network outages, hard drive malfunctions, operating system crashes and other similar events can cause databases to become unavailable. The need to recover from these types of events has led to the emergence of many cold standby/hot standby tools that can help to facilitate disaster recovery remotely. In this section, we'll explain how to backup deployed applications in addition to backing up and restoring any MySQL and Redis databases you might be using. +In this section, we'll discuss another aspect of application management: data backup and recovery on production servers. We often encounter situations where production servers don't behave as we expect them to. Server network outages, hard drive malfunctions, operating system crashes and other similar events can cause databases to become unavailable. The need to recover from these types of events has led to the emergence of many cold standby/hot standby tools that can help to facilitate disaster recovery remotely. In this section, we'll explain how to backup deployed applications in addition to backing up and restoring any MySQL and Redis databases you might be using. ## Application Backup @@ -174,7 +174,7 @@ As you can see, importing and exporting database is a fairly simple matter. If y ## Redis backup -Redis is one of the most popular NoSQL databases, and both hot and cold backup techniques can also be used in systems which use it. Like MySQL, Redis also supports master/slave mode, which is ideal for implementing hot backups (refer to Redis' official documentation to learn learn how to configure this; the process is very straightforward). As for cold backups, Redis routinely saves cached data in memory to the database file on-disk. We can simply use the rsync backup method described above to synchronize it with a non-local machine. +Redis is one of the most popular NoSQL databases, and both hot and cold backup techniques can also be used in systems which use it. Like MySQL, Redis also supports master/slave mode, which is ideal for implementing hot backups (refer to Redis' official documentation to learn how to configure this; the process is very straightforward). As for cold backups, Redis routinely saves cached data in memory to the database file on-disk. We can simply use the rsync backup method described above to synchronize it with a non-local machine. ## Redis recovery diff --git a/en/13.1.md b/en/13.1.md index b024de93..4ccb1585 100644 --- a/en/13.1.md +++ b/en/13.1.md @@ -4,7 +4,7 @@ Anything you intend to do well must first be planned well. In our case, our inte ## GOPATH and project settings -Let's proceed by assuming that our GOPATH points to a folder with with an ordinary directory name (if not, we can easily set up a suitable directory and set its path as the GOPATH). As we've describe earlier, a GOPATH can contain more than one directory: in Windows, we can set this as an environment variable; in linux/OSX systems, GOPATH can be set using `export`, i.e: `export gopath=/path/to/your/directory`, as long as the directory which GOPATH points to contains the three sub-directories: `pkg`, `bin` and `src`. Below, we've placed the source code of our new project in the `src` directory with the tentative name `beelog`. Here are some screenshots of the Windows environment variables as well as of the directory structure. +Let's proceed by assuming that our GOPATH points to a folder with an ordinary directory name (if not, we can easily set up a suitable directory and set its path as the GOPATH). As we've describe earlier, a GOPATH can contain more than one directory: in Windows, we can set this as an environment variable; in linux/OSX systems, GOPATH can be set using `export`, i.e: `export gopath=/path/to/your/directory`, as long as the directory which GOPATH points to contains the three sub-directories: `pkg`, `bin` and `src`. Below, we've placed the source code of our new project in the `src` directory with the tentative name `beelog`. Here are some screenshots of the Windows environment variables as well as of the directory structure. ![](images/13.1.gopath.png?raw=true) diff --git a/en/14.6.md b/en/14.6.md index 5ad61fc9..0bae3163 100644 --- a/en/14.6.md +++ b/en/14.6.md @@ -10,7 +10,7 @@ In fact, `net/http/pprof` simply exposes runtime profiling data from the `runtim ## pprof support in Beego -The Beego framework currently supports pprof, however it is not not turned on by default. If you need to test the performance of your application, (for instance by viewing the execution goroutine) such information from Go's default package "net/http/pprof" already has this feature. Because beego has repackaged the ServHTTP function, you can not open the default feature included in pprof. This resulted in beego supporting pprof internally. +The Beego framework currently supports pprof, however it is not turned on by default. If you need to test the performance of your application, (for instance by viewing the execution goroutine) such information from Go's default package "net/http/pprof" already has this feature. Because beego has repackaged the ServHTTP function, you can not open the default feature included in pprof. This resulted in beego supporting pprof internally. - First in our `beego.Run` function, we choose whether or not to automatically load the performance pack according to our configuration variable (in this case, PprofOn): diff --git a/es/01.0.md b/es/01.0.md index 55479003..c3b332ee 100644 --- a/es/01.0.md +++ b/es/01.0.md @@ -16,5 +16,5 @@ En este capítulo, te mostraremos cómo instalar y configurar tu propio ambiente ## Links -- [Directorio](preface.md) +- [Índice](preface.md) - Sección siguiente: [Instalación](01.1.md) diff --git a/es/01.1.md b/es/01.1.md index 25388652..726ac3c0 100644 --- a/es/01.1.md +++ b/es/01.1.md @@ -28,21 +28,21 @@ En Windows, debes instalar MinGW antes para poder instalar gcc. No olvides confi El equipo Go usa [Mercurial](http://mercurial.selenic.com/downloads/) para manejar su código fuente, por lo que debes instalar esta herramienta para poder bajar el código fuente de Go. En este punto ejecuta los siguientes comandos para clonar el código fuente de Go y compilarlo. (Clonará el código fuente en tú directorio actual. Cambia tu ruta actual antes de continuar. Esto puede tomar algún tiempo.) - +``` hg clone -u release https://code.google.com/p/go cd go/src ./all.bash - +``` Una instalación exitosa finalizará con el mensaje "ALL TESTS PASSED." En Windows puedes lograr lo mismo ejecutando `all.bat`. Si estas usando Windows el paquete de instalación establecerá tus variables de entorno automáticamente. En sistemas tipo Unix necesitas establecer estas variables de la siguiente manera. (Si tu versión de Go es mayor que 1.0 no debes establecer $GOBIN ya que estará relacionada automáticamente a tu $GOROOT/bin del que hablaremos en la sección siguiente) - +``` export GOROOT=$HOME/go export GOBIN=$GOROOT/bin export PATH=$PATH:$GOROOT/bin - +``` Si ves la siguiente información en tu pantalla, todo está listo. ![](images/1.1.mac.png?raw=true) @@ -66,11 +66,11 @@ Si eres un usuario de Mac recomiendo fuertemente que bajes el paquete de 64-bits Los usuarios de Linux pueden escribir `uname -a` en la terminal para ver la información del sistema. Un sistema operativo de 64-bits mostrará lo siguiente: - +``` x86_64 x86_64 x86_64 GNU/Linux // algunas versiones como Ubuntu 10.04 mostrarán de la siguiente forma x86_64 GNU/Linux - +``` En cambio los sistemas operativos de 32-bits mostrarán: i686 i686 i386 GNU/Linux @@ -92,32 +92,32 @@ Ve a la [página de descarga](https://golang.org/dl/), escoge `go1.5.3.windows-3 ### GVM GVM es una herramienta de control de múltiples versiones de Go desarrollado por terceros, parecida a rvm para ruby. Es bien fácil de utilizar. Instala gvm escribiendo los siguientes comandos en tu terminal: - +``` bash < <(curl -s https://raw.github.com/moovweb/gvm/master/binscripts/gvm-installer) - +``` Luego instalamos Go usando los siguientes comandos: - +``` gvm install go1.0.3 gvm use go1.0.3 - +``` Cuando el proceso ha finalizado estamos listos. ### apt-get Ubuntu es la versión de escritorio más popular de Linux. Utiliza `apt-get` para manejar paquetes. Podemos instalar Go usando los siguientes comandos. - +``` sudo add-apt-repository ppa:gophers/go sudo apt-get update sudo apt-get install golang-stable - +``` ### Homebrew Homebrew es una herramienta para manejar software comúnmente usada en la Mac. Simplemente escribe lo siguiente para instalar Go. - +``` brew install go - +``` ## Links -- [Directorio](preface.md) +- [Índice](preface.md) - Sección anterior: [Configurando el entorno de Go](01.0.md) - Sección siguiente: [$GOPATH y el ambiente de trabajo](01.2.md) diff --git a/es/01.2.md b/es/01.2.md index 80b064de..c6e38004 100644 --- a/es/01.2.md +++ b/es/01.2.md @@ -5,9 +5,9 @@ Todos los comandos de Go dependen de una importante variable de entorno llamada $GOPATH. Favor notar que esta no es la variable $GOROOT que es la que muestra dónde Go está instalado. Esta variable apunta al ambiente de trabajo de Go en tu computadora (Yo uso esta ruta en mi computadora; si no tienes la misma estructura de directorios, por favor reemplaza por la que tengas). En sistemas tipo Unix, la variable debe usar así: - +``` export GOPATH=/home/apple/mygo - +``` En Windows, necesitas crear una nueva variable de entorno llamada GOPATH, luego estable su valor a `c:\mygo` (Este valor depende dónde tu ambiente de trabajo esté ubicado) Está bien tener más de una ruta de ambiente de trabajo en $GOPATH, pero recuerda que debes usar `:`(`;` en Windows) para separarlos. En este punto, `go get` guardará el contenido de tu primera ruta en $GOPATH. @@ -22,17 +22,17 @@ En este libro, utilizo `mygo` cómo mi única ruta en $GOPATH. ## Directorio de paquetes -Crea archivos de código fuente de paquetes de la siguiente forma `$GOPATH/src/mymath/sqrt.go` (`mymath` es el nombre del paquete) ( ***El autor utiliza `mymath` cómo su nombre de paquete, el mismo nombre para el directorio que contiene los archivos de código fuente del paquete) +Crea archivos de código fuente de paquetes de la siguiente forma `$GOPATH/src/mymath/sqrt.go` (`mymath` es el nombre del paquete) ( **El autor utiliza `mymath` cómo su nombre de paquete, el mismo nombre para el directorio que contiene los archivos de código fuente del paquete**) Cada vez que creas un paquete, deberías crear un nuevo folder en el directorio `src`. Los nombres de los directorios usualmente son los mismos que el paquete que vas a utilizar. Puedes tener directorios de múltiples niveles si lo deseas. Por ejemplo, si creas el directorio `$GOPATH/src/github.com/astaxie/beedb`, entonces la ruta del paquete sería `github.com/astaxie/beedb`. El nombre del paquete será el último directorio en tu ruta, que es `beedb` en este caso. Ejecuta los siguientes comandos. (Ahora el autor mostrará unos ejemplos) - +``` cd $GOPATH/src mkdir mymath - +``` Crea un nuevo archivo llamado `sqrt.go`, escribe el siguiente contenido en el archivo. - +``` // Código fuente de $GOPATH/src/mymath/sqrt.go package mymath @@ -43,7 +43,7 @@ Crea un nuevo archivo llamado `sqrt.go`, escribe el siguiente contenido en el ar } return z } - +``` Ahora el directorio de mi paquete ha sido creado y su código ha sido escrito. Recomiendo que uses el mismo nombre para tus paquetes y sus directorios correspondientes y que los directorios contenga todo el código fuente del paquete. ## Compilar paquetes @@ -54,24 +54,24 @@ Ya hemos creado nuestra paquete, pero cómo lo compilamos para propósitos prác 2. Ejecuta el comando de arriba, con la diferencia de que suministras el nombre del archivo como parámetro `go install mymath`. Después de compilar podemos abrir el siguiente directorio. - +``` cd $GOPATH/pkg/${GOOS}_${GOARCH} // puedes ver que el archivo fue generado mymath.a - +``` Este archivo cuyo sufijo es `.a`, es el archivo binario de nuestro paquete. Cómo lo usamos? Obviamente, necesitamos crear una nueva aplicación para utilizarlo. Crea una nueva aplicación llamada `mathapp`. - +``` cd $GOPATH/src mkdir mathapp cd mathapp vim main.go - +``` código - +``` //$GOPATH/src/mathapp/main.go código fuente package main @@ -83,23 +83,23 @@ código func main() { fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) } - +``` Para compilar esta aplicación necesitas cambiar al directorio de la aplicación que en este caso es `$GOPATH/src/mathapp`, luego ejecuta el comando `go install`. Ahora deberías ver un archivo ejecutable llamado `mathapp` que se ha generado en el directorio `$GOPATH/bin/`. Para correr este programa usa el comando `./mathapp`. Deberías de ver el siguiente contenido en tu terminal: - +``` Hello world. Sqrt(2) = 1.414213562373095 - +``` ## Instala paquete remotos Go tiene una herramienta para instalar paquetes remotos, es el comando llamado `go get`. Soporta la mayoría de comunidades de código libre, incluyendo Github, Google Code, BitBucket y Launchpad. - +``` go get github.com/astaxie/beedb - +``` Puedes usar `go get -u …` para actualizar tus paquetes remotos e incluso instalará todas sus dependencias. Esta herramienta usará diferente herramientas de control de versiones para las diferentes plataformas de código libre. Por ejemplo, `git` para Github y `hg` para Google Code. Debido a esto, debes instalar estas herramientas de control de versiones antes de usar `go get`. Después de ejecutar los comandos anteriormente descritos, la estructura de directorios debería verse de la siguiente forma: - +``` $GOPATH src |-github.com @@ -110,7 +110,7 @@ Después de ejecutar los comandos anteriormente descritos, la estructura de dire |-github.com |-astaxie |-beedb.a - +``` Actualmente, `go get` clona el código fuente a la ruta $GOPATH/src, luego ejecuta `go install`. Puedes usar paquetes remotos de la misma forma que usas paquetes locales. @@ -120,7 +120,7 @@ Puedes usar paquetes remotos de la misma forma que usas paquetes locales. ## Estructura completa del directorio Si has seguido todos los pasos anteriores, la estructura de tu directorio se debería ver de la siguiente forma. - +``` bin/ mathapp pkg/ @@ -139,13 +139,13 @@ Si has seguido todos los pasos anteriores, la estructura de tu directorio se deb beedb/ beedb.go util.go - +``` Ahora puedes ver la estructura de directorios claramente; `bin` contiene los archivos ejecutables, `pkg` contiene los archivos compliados y `src` los archivos de código fuente del paquete. (El formato de las variables de entorno en Windows es `%GOPATH%`, sin embargo este libro sigue principalmente el estilo Unix, así que si eres un usuario Windows debes reemplazarlo apropiadamente) ## Links -- [Directorio](preface.md) +- [Índice](preface.md) - Sección anterior: [Instalación](01.1.md) - Sección siguiente: [Comandos Go](01.3.md) diff --git a/es/01.3.md b/es/01.3.md index 80a74e05..4acf28ea 100644 --- a/es/01.3.md +++ b/es/01.3.md @@ -31,7 +31,7 @@ Este comando es para pruebas de compilación. Compilará paquetes dependientes d ## go clean Este comando es para limpiar los archivos que son generados por los compiladores, incluyendo los siguientes archivos: - +``` _obj/ // viejo directorio de object, dejado por Makefiles _test/ // viejo directorio de test, dejado por Makefiles _testmain.go // viejo directorio de gotest, dejado por Makefiles @@ -42,7 +42,7 @@ Este comando es para limpiar los archivos que son generados por los compiladores DIR(.exe) // generado por go build DIR.test(.exe) // generado por go test -c MAINFILE(.exe) // generado por go build MAINFILE.go - +``` Usualmente utilizo este comando para limpiar mis archivos antes de subir mi proyecto a Github. Estas son herramientas útiles para pruebas locales, pero inútiles para el control de versiones. ## go fmt @@ -54,12 +54,12 @@ Usualmente usamos `gofmt -w` en vez de `go fmt`. El último, no rescribirá tus ## go get Este comando es para obtener paquetes remotos. Hasta el momento soporta BitBucket, Github, Google Code y Launchpad. Actualmente existen dos cosas que suceden después de ejecutar este comando. La primera es que Go descarga el código fuente, luego ejecuta `go install`. Antes de que utilices este comando, asegúrate que tienes instaladas todas las herramientas relacionadas. - +``` BitBucket (Mercurial Git) Github (git) Google Code (Git, Mercurial, Subversion) Launchpad (Bazaar) - +``` Para poder usar este comando, debes instalar estas herramientas correctamente. No olvides establecer `$PATH`. Por cierto, también soporta nombres de dominios customizados. Usa `go help remote` para más detalles al respecto. ## go install @@ -69,12 +69,12 @@ Este comando compila todos los paquetes y genera archivos, luego los mueve a `$G ## go test Este comando carga todos los archivos cuyos nombres incluyen `*_test.go` y genera archivos de pruebas, luego muestra información que se ve de la siguiente forma. - +``` ok archive/tar 0.011s FAIL archive/zip 0.022s ok compress/gzip 0.033s ... - +``` Prueba todos tus archivos de prueba por defecto. Usa el comando `go help testflag` para más detalles. ## go doc @@ -88,17 +88,17 @@ Ejecuta el comando `godoc -http=:8080`, luego abre `127.0.0.1:8080` en tu navega ## Otros comandos Go provee comandos adicionales a los que ya mostramos. - +``` go fix // actualiza código de una vieja versión antes de go1 a una nueva versión después de go1 go version // muestra información de tu versión de Go go env // muestra las variables de entorno relacionadas a Go go list // lista todos los paquetes instalados go run // compila los archivos temporales y ejecuta la aplicación - +``` También hay más detalles de los comandos que hemos hablado, puedes usar el comando `go help ` para mostrarlos. ## Links -- [Directorio](preface.md) +- [Índice](preface.md) - Sección anterior: [$GOPATH y el ambiente de trabajo](01.2.md) - Sección siguiente: [Herramientas de Desarrollo para Go](01.4.md) diff --git a/es/01.4.md b/es/01.4.md index 10bd4df0..96829526 100644 --- a/es/01.4.md +++ b/es/01.4.md @@ -54,14 +54,14 @@ Características de LiteIDE - Instala gocode Debes instalar gocode para poder usar el completamiento inteligente - +``` go get -u github.com/nsf/gocode - +``` - Entorno de compilación Cambia la configuración en LiteIDE para que se ajuste a tu sistema operativo. En Windows y usando al versión de 64-bits de Go, debes usar win64 cómo la configuración de entorno en la barra de herramientas. Luego escoge `Options`, busca `LiteEnv` en la lista de la izquierda y abre el archivo `win64.env` en la lista de la derecha. - +``` GOROOT=c:\go GOBIN= GOARCH=amd64 @@ -70,11 +70,11 @@ Características de LiteIDE PATH=%GOBIN%;%GOROOT%\bin;%PATH% 。。。 - +``` Reemplaza `GOROOT=c:\go` con tu ruta de instalación de Go y guarda. Si tienes MinGW64, agrega `c:\MinGW64\bin` a la variable de entorno de tu path para soportar `cgo`. En Linux y usando la versiónd de 64-bits de Go, debes escoger linux64 como la configuración de entorno en la barra de herramientas. Luego escoge `Options`, busca `LiteEnv` en la lista de la izquierda y abre el archivo `linux64.env` en la lista de la derecha. - +``` GOROOT=$HOME/go GOBIN= GOARCH=amd64 @@ -83,7 +83,7 @@ Características de LiteIDE PATH=$GOBIN:$GOROOT/bin:$PATH 。。。 - +``` Reemplaza `GOROOT=$HOME/go` con tu ruta de instalación de Go y guarda. - $GOPATH $GOPATH es la ruta que contiene la lista de proyectos. Abre la herramienta de comandos (o presiona `Ctrl+` en LiteIDE), luego escribe `go help gopath` para más detalles. @@ -141,22 +141,22 @@ Vim es un editor de texto popular para los programadores, que evolucionó de su Imagen 1.8 Completamiento inteligente en Vim para Go 1. Realce de sintaxis para Go - +``` cp -r $GOROOT/misc/vim/* ~/.vim/ - +``` 2. Habilitando el realce de sintaxis - +``` filetype plugin indent on syntax on - +``` 3. Instalar [gocode](https://github.com/nsf/gocode/) - +``` go get -u github.com/nsf/gocode - +``` gocode se instalará por defecto en `$GOBIN` 4. Configura [gocode](https://github.com/nsf/gocode/) - +``` ~ cd $GOPATH/src/github.com/nsf/gocode/vim ~ ./update.bash ~ gocode set propose-builtins true @@ -166,7 +166,7 @@ Imagen 1.8 Completamiento inteligente en Vim para Go ~ gocode set propose-builtins true lib-path "/home/border/gocode/pkg/linux_amd64" - +``` Explicación de la configuración de gocode: propose-builtins: especifica si abrir o no el completamiento inteligente; falso por defecto. @@ -183,16 +183,16 @@ Emcas es la llamada arma de Dios. No es solamente un editor, sino un poderoso ID Imagen 1.10 Panel principal de Emacs editando Go 1. Realce de sintaxis - +``` cp $GOROOT/misc/emacs/* ~/.emacs.d/ - +``` 2. Instalar [gocode](https://github.com/nsf/gocode/) - +``` go get -u github.com/nsf/gocode - +``` gocode se instalará por defecto en `$GOBIN` 3. Configura [gocode](https://github.com/nsf/gocode/) - +``` ~ cd $GOPATH/src/github.com/nsf/gocode/vim ~ ./update.bash ~ gocode set propose-builtins true @@ -202,14 +202,14 @@ Imagen 1.10 Panel principal de Emacs editando Go ~ gocode set propose-builtins true lib-path "/home/border/gocode/pkg/linux_amd64" - +``` 4. Instalar [Auto Completion](http://www.emacswiki.org/emacs/AutoComplete) Descarga y descomprime - +``` ~ make install DIR=$HOME/.emacs.d/auto-complete - +``` Configura el archivo ~/.emacs - +``` ;;auto-complete (require 'auto-complete-config) (add-to-list 'ac-dictionary-directories "~/.emacs.d/auto-complete/ac-dict") @@ -217,10 +217,10 @@ Imagen 1.10 Panel principal de Emacs editando Go (local-set-key (kbd "M-/") 'semantic-complete-analyze-inline) (local-set-key "." 'semantic-complete-self-insert) (local-set-key ">" 'semantic-complete-self-insert) - +``` Visita este [link](http://www.emacswiki.org/emacs/AutoComplete) para más detalles. 5. Configura .emacs - +``` ;; golang mode (require 'go-mode-load) (require 'go-autocomplete) @@ -305,6 +305,7 @@ Imagen 1.10 Panel principal de Emacs editando Go (interactive) (show-all) (shell-command-on-region (point-min) (point-max) "go tool fix -diff")) +``` 6. Felicitaciones, ya estás listo! Speedbar está cerrada por defecto -remueve los símbolos de comentarios en la línea `;;(speedbar 1)` para habilitar esta característica o puedes usarla a través de `M-x speedbar`. ## Eclipse @@ -327,9 +328,9 @@ Imagen 1.1 Panel principal de Eclipse editando Go Necesitas instalar git en Windows, usualmente usamos [msysgit](https://code.google.com/p/msysgit/) Instala gocode en la herramienta de comandos - +``` go get -u github.com/nsf/gocode - +``` También puedes instalar desde el código fuente si gustas. 4. Descarga e instala [MinGW](http://sourceforge.net/projects/mingw/files/MinGW/) 5. Configura los plugins. @@ -390,10 +391,10 @@ Las personas que han trabajado con Java deben estar familiarizadas con este IDE. Introduce la ubicación de tu Go sdk en el siguiente paso - básicamente es tu $GOROOT. -( ***Revisa este [artíclo de blog](http://wuwen.org/tips-about-using-intellij-idea-and-go/) para una configuración paso a paso *** ) +(**Revisa este [artíclo de blog](http://wuwen.org/tips-about-using-intellij-idea-and-go/) para una configuración paso a paso** ) ## Links -- [Directorio](preface.md) +- [Índice](preface.md) - Sección anterior: [Comandos Go](01.3.md) - Sección siguiente: [Resumen](01.5.md) diff --git a/es/01.5.md b/es/01.5.md index bfeab561..29186276 100644 --- a/es/01.5.md +++ b/es/01.5.md @@ -4,6 +4,6 @@ En este capítulo hablamos acerca de como instalar Go usando tres métodos difer ## Links -- [Directorio](preface.md) +- [Índice](preface.md) - Sección anterior: [Herramientas de Desarrollo para Go](01.4.md) - Siguiente capítulo: [Conocimiento básico de Go](02.0.md) diff --git a/es/02.0.md b/es/02.0.md new file mode 100644 index 00000000..8aa1e9dc --- /dev/null +++ b/es/02.0.md @@ -0,0 +1,17 @@ +# 2 Go, Conocimiento básico + +Go es un lenguaje de progrmación compilado y pertenece a la familia de C. Sin embargo su velocidad de compilación es mayor que otros lenguajes de la familia de C. Tiene únicamente 25 palabras reservadas ... ¡Incluso menos que las 26 letras del alfabeto Inglés! Vamos a echarle un vistazo a a estas palabras reservadas antes de comenzar + + break default func interface select + case defer go map struct + chan else goto package switch + const fallthrough if range type + continue for import return var + +En este capítulo voy a enseñarles algún conocimiento básico de Go. Encontrarás que concizo es el lenguaje de programación Go y la belleza del diseño del lenguaje. Programar puede ser muy dvertido en Go. Despuś de completar este capítulo, estarás familiarizado con las palabras reservadas de arriba. + +## Enlaces + +- [Directorio](preface.md) +- Capítulo anterior: [Capítulo 1 Resumen](01.5.md) +- Siguiente sección: ["Hola, Go"](02.1.md) diff --git a/es/02.1.md b/es/02.1.md new file mode 100644 index 00000000..a4d38fa7 --- /dev/null +++ b/es/02.1.md @@ -0,0 +1,103 @@ +## ¿Qué hace a Go diferente de otros lenguajes + +El lenguaje de programación Go fue creado con una meta en mente, ser capaz de construir aplicaciones web escalables para grandes audiencias ocn un equipo grande. Esta es la razón por la cual ellos hicieron el lenguaje tan estandarizado como fuera posible, por esto razón la herramienta `gofmt` y el estricto uso de las guías base del lenguaje fueron creados por el motivo de no tener dos caras en la base del desarrollo, en otros lenguajes existen guerras religiosas en ¿Dónde debo colocar la llave abierta? +``` + public static void main() { + + } + + or + + public static void main() + { + + } + ``` +O para python debo usar 4 espacios, 6 espacios o tabulación y otras preferencias de usuario. + +Esto puede parecer ser un problema superficial en la superficie, pero cuando la base de código crece y muchas mas personas están trabajando en la misma base de código, entonces es dificil mantener la "belleza" del código. Si usted conoce python, entonces usted debe ser conciente del PEP8, el cual es un conjunto de guías sobre como escribir código elegantemente. Vivimos en un mundo donde los robots pueden conducir un carro, entonces nosotros no solo debemos escribir código, debemos escribir código elegante. + +Para otros lenguajes existen muchas variables cuando es hora de escribir código, cada lenguaje es bueno para su caso de uso, pero Go es un caso especial en esa superficie porque fue diseñado por una compañia la cual es sinónimo de Internet (y de la computación distribuida), tipicamente el flujo de escribir código va de Python a Java a C++ para propósitos de optimización, pero el problema es que casi todos los lenguajes de programación fueron escritos décadas atrás, cuando 1GB de almacenamiento tenía un costo mucho mas alto comparado con hoy en día, donde el costo de almacenamiento y computación se ha vuelto mas económico. Los computadores se están volviendo multinúcleo estos días y los "lenguajes viejos" no aprovechan la concuerrencia de la manera que Go lo hace, no porque esos lenguajes sean malos, simplemente porque el caso de uso no era reelevante cuando esos lenguajes evolucionaron. + +Entonces para mitigar todos los problemas que Google enfrentó con las herramientas actuales, ellos escribieron un sustema de lenguaje llamado Go, ¡el cual estás a punto de aprendeer! Hay muchas ventajas para usar Golang, y también muchas desventajas, para cada moneda hay dos caas. Pero hay avances significativos en lugares donde el formato de código, desde que llos diseñaron el lenguaje en una manera que no habrían guerrras con como darle formato al código. El código de formato de Go, escrito por cualquier persona en el mundo (asumiendo que usan `gofmt`) se verá exactamente igual, ¡esto parecerá no importar hasta que trabajes en un equipo! también cuando una compañia usa revisión de código automatizada o alguna otra técnica lujosa, donde en otros lenguajes que no tienen un estándar en las reglas de formato, todo el código estará comprometido, ¡pero no en Go. + +Go fue diseñado con la concurrencia en mente, por favor note que paralelismo != concurrencia. Hay un post maravilloso escrito por Bob Pike en el blog de Go, blog.golang.org, que usted encontraréa ahí y vale la pena leerlo. + +Otro cambio muy importante que Go trajo a la programación y que yo personalmente amo es le concepto de `GOPATH`, los tiempos donde tenías que crear una carpeta llamadacode y entonces crear espacios de trabajo para eclipse y otros. Ahora usted puede tener un árbol de carpetas para el código en Go y se mantendrá actualizado automáticamente por el administrador de paquetes. También bajo el código que estamos recomendando crear carpetas con cada dominio específico o el dominio de github, por ejemplo yo creé un manejador de tareas usando Go, entonces creé un conjunto de carpetas +`~/go/src/github.com/thewhitetulip/Tasks` Nota: En sistemas * nix `~` se refiere al directorio del usuario, que en windows es equivalente a `C:\\Users\\username` +Ahora el `~/go/` es el universo de código Go en su máquina, este es solo una significante mejora en comparación con otros lenguajes, entonces podemos almacenar código eficientemente sin molestias, esto puede parecer raro al comienzo pero tiene un montón de sentido a comparación de los nombres de paquete en otros lenguajes que usan sistemas como el del dominio inverso. + +nota: junto con el src, existen dos carpetas adicionales, que son el `pkg` y el `bin` que es para los archivos binarios + +Estas ventajas de `GOPATH` no están restringidas a guardar código en una carpeta particular, pero cuando tu tienes creado 5 paquetes para tu proyecto entonces tu no vas a tener tiempo de importarlos como `import ./db`, usted puede escribir `import "github.com/thewhitetulip/Tasks/db"`, entonces esto hará un `go get` a mi repositorio y la herramienta `go` encontrará el paquete de `github.com/...` si no estaba descargado inicialmente, eso estandariza un montón de cosas en la disciplina de la programación. + +Algunos se quejan que los creadores de go han ignorado toda la investigación de lengaujes hecha los pasados 30 años, bueno, esto puede ser verdad, pero de nuevo, no puedes crear un lenguaje que todo mundo ame, siempre hay alguna u otra causa o restricción que hace a los creadores considerar, y considerando todas las ventajas al menos para el desarrollo web, yo no creo que algún lenguaje se acerque tanto a las ventajas que ha alcanzado `Go`, incluso si ignoras todo lo que he dicho arriba, Go es u lenguaje compilado, lo que significa que en producción no vas a tener que configurar un `JVM` o un `virtualenv`, ¡solo vas a tener un un estático binario! y como un cubo de hielo en un pastel, todas las librerías modernas están en la librería estándar, como `http`, lo cual es una ventaja mayor, es la razón principal por la que puedes crear aplicaciones web en Go sin tener que usar un framework de terceros. + +# 2.1 Hola, Go + +Antes de comenzar a contruir una aplicación en Go, necesitamos aprender como escribir un simple programa. No puedes esperar construir un edificio sin saber como construir sus fundamentos. Por esta razón, vamos a aprender la sintaxis básica para correr algunos programas en esta sección. + +## Programa + +De acuerdo con la prácica internaciona, antes de que tu puedas contruir en algún lenguaje, usted querrá escribir un programa que imporima "Hola Mundo". + +¿Estamos listos? ¡Empecemos! +``` + package main + + import "fmt" + + func main() { + fmt.Printf("Hola, mundo o 你好,世界 o καλημ ́ρα κóσμ o こんにちは世界\n") + } +``` +Esto imprime la siguiente información +``` + Hola, mundo o 你好,世界 o καλημ ́ρα κóσμ o こんにちは世界 +``` +## Explanation + +Una cosa que usted debería tener en cuanta es que los programas en Go están compuestos por `package`s o paquetes. + +`package ` (En este caso es `package main`) nos dice que el archivo pertenece al paquete `main` y la palabra main nos dice que este paquete será compilado a un programa en vez de a un archivo cuya extensión es `.a`. + +Cada programa ejecutable tendrá un solo paquete `main` y una única función de enrtada llamada `main`sin ningún argumento de entrada o salidad en el paquete `main`. + +En orden de imprimir `Hola, mundo ...` llamamos a una función llamada `Printf`. Esta funcieon viene del paquete `fmt`, entonces importamos este paquete en la tercera línea de código, que es `import "fmt"` + +La manera de pensar sobre estos paquetes es similar a Python, y hay muchas otras ventajas: Modularidad (dividir tu programa en muchos módulos) y reusabilidad (cada módulo puede ser reusado en muchos programas). Nosotros solo hemos hablado sobre los conceptos relacionado con los paquetes, y luego daremos un paseo por nuestros propios paquetes, después. + +En la quinta línea, usamos la palabra reservada `func` para definir la función `main`. El cuerpo d ela función está dentro de `{}`, como en C, C++ o Java. + +Como puedes ver, no hay argumentos. Aprenderemos como escribir una función con argumentos en un segundo, y también hay funciones que no retornan nada o que retornan muchos valores. + +En la sexta lína, llamamos la la funcieon `Printf` la cual pertenece al paquete `fmt`. Esta fue llamada por la sintaxis `.`, que es mucho el estilo de Python. + +Como mencionamos en el capítulo 1, el nombre de paquete y el nombre de la carpeta que contiene el archivo puede ser diferente. Aquí el `nombreDelPaquete` viene del nombre en `package `, no del nombre de la carpeta. + +Usted se puede dar cuenta que el ejemplo de arriba contiene catacteres no ASCII. El propósito de mostrar esto es para decir que Go soporta UTF-8 por defecto. Usted puede usar cualquier caracter UTF-8 en sus programas. + +Cada archivo en Go está en algún pauqete, y el paquete debería estar en una carpeta distina en el GOPATH, pero el paquete `main` no requiere estar en una carpeta llamada `main`. ¡Este es un aspecto que ellos dejaron fuera de la estandarización! Pero si usted escoge crear una carpeta llamada main, tiene que asegurarse de ejecutar el binario correctamente. Un programa en go no puede tener mas de 1 archivo main. + +`~/go/src/github.com/thewhitetulip/Tasks/main $ go build` +`~/go/src/github.com/thewhitetulip/Tasks $ ./main/main` + +El detalle aquí es que cuando está usando algun archivo estático o algo mas, entonces vas a tener que ejecutar el archivo desde la raiz de la aplicación, como se ve en la segunda línea de arriba. Estoy ejecutando el binario `main` *fuera* del paquete main, en algún momento notarás que nuestra aplicación no está funcionando de la manera que queremos, por esta razón, para evitar problemas, mantenga esto en mente. + +Una caosa que usted puede notar es que Go no usa punto y comas para terminar una sentencia, bueno, si lo hace, solo que el usuario no tiene que preocuparse por colocar los punto y comas, el compilador agregas los punto y comas al final del gocode, por esta razón esto es un error de sintaxis +``` + func main () + { + } +``` +porque el compilador coloca un punto y coma al final de `main()` lo cual es un error de sintaxis , y esto ayuda a evitar guerras religiosas. Espero que combinen `vim` y `emacs` y creen un editor universal que nos ayude a evitarnos mas guerras, pero por ahora vamos a concentrarnos en aprender Go. + +## Conclusión + +Go usa `package` (como lo módulos en Python) para organizar los programas. La función `main.main()` (esta funcieon debe estar en el paquete `main`) es el punto de entrada de cualquier programa. Go estandariza el lenguaje y la mayoría de la metodología de programación, ahorrándole tiempo a los desarrolladores que se pierden en guerras religiosas. Solo puede existir un paquete main y solo una función main dentro del paquete main de Go. Go soporta caracteres UTF-8 porque uno de los creadores de Go es el creador de UTF-8, así que Go soporta múltiples lenguajes desde el momento en que nació. + +## Enlaces + +- [Índice](preface.md) +- Sección anterior: [Go, Conocimiento básico](02.0.md) +- Siguiente sección: [Fundamentos de Go](02.2.md) diff --git a/es/02.2.md b/es/02.2.md new file mode 100644 index 00000000..4f7f2b60 --- /dev/null +++ b/es/02.2.md @@ -0,0 +1,478 @@ +# 2.2 Principios de Go + +En esta sección, vamos a aprender como definir constantes, variables con tipos básicos y algunos conocimientos mas de la programación en Go. + +## Definición de variables + +Tenemos muchas formas diferentes de definir variables con la sintaxis de Go. + +Utilizando la palabra reservada `var` es la forma básica de definir una variable, observe como en Go se coloca el tipo de variable luego del nombre de la misma. +``` + // definición de una variables de nombre “variableName” y tipo "type" + var variableName type +``` +Definición de múltiples variables. +``` + // definimos tres variables donde su tipo es "type" + var vname1, vname2, vname3 type +``` +Definición de una variable con un valor inicial. +``` + // definición de una variable de nombre “variableName”, tipo "type" y valor "value" + var variableName type = value +``` +Definición de múltiples variables con valores iniciales. +``` + /* + Definición de tres variables de tipo "type", y sus valores iniciales. + vname1 de valor v1, vname2 de valor v2, vname3 de valor v3 + */ + var vname1, vname2, vname3 type = v1, v2, v3 +``` +¿Crees que es muy tedioso utilizar las anteriores formas para definir variables? Entonces no te preocupes, porque el equipo de desarrollo de Go también se encontró con este problema. +Por lo que si deseas definir una variable con valores iniciales, podemos omitir el tipo de cada una, +por lo que el código quedaría algo así: +``` + /* + Definición de tres variables de tipo "type", y sus valores iniciales. + vname1 de valor v1,vname2 de valor v2,vname3 de valor v3 + */ + var vname1, vname2, vname3 = v1, v2, v3 +``` +Bueno, se que aun no es lo suficientemente sencillo. Vamos a ver como lo mejoramos. +``` + /* + Definición de tres variables de tipo "type", y sus valores iniciales. + vname1 de valor v1,vname2 de valor v2,vname3 de valor v3 + */ + vname1, vname2, vname3 := v1, v2, v3 +``` +Ahora se ve mucho mejor. Usa `:=` para reemplazar a `var` y `type`, esto se llama declaración breve. Pero espera, esto tiene una limitante, esta forma de declaración solo puede ser utilizada dentro de una función. Si intentas utilizarla fuera del cuerpo de una función vas a recibir un error de compilación. Por lo que normalmente utilizamos `var` para definir variables globales, y podemos utilizar la declaración breve en `var()`. + +`_` (guión bajo) es un nombre especial de variable, cualquier valor que le sea otorgado será ignorado. Por ejemplo, le otorgamos `35` a `b`, y descartamos el `34`.( ***Este ejemplo nos muestra como esto funciona. Parece inútil aquí porque normalmente lo utilizamos cuando tomamos los valores de retorno de una función.*** ) +``` + _, b := 34, 35 +``` +Si no utilizas alguna de las variables declaradas en el programa, el compilador les lanzara un error. Intenta compilar el siguiente código, ve que sucede. +``` + package main + + func main() { + var i int + } +``` +## Constantes + +Las llamadas constantes son los valores que se determinan en el momento de compilación, y que no se pueden modificar durante la ejecución. En Go, podemos utilizar números, booleanos o cadenas como tipos de constantes. + +Definimos una constante de la siguiente manera. +``` + const nombreConstante = valor + // podemos asignar el tipo de una constante si es necesario + const Pi float32 = 3.1415926 +``` +Otros ejemplos. +``` + const Pi = 3.1415926 + const i = 10000 + const MaxThread = 10 + const prefix = "astaxie_" +``` +## Tipos básicos + +### Booleanos + +En Go, podemos usar `bool` para definir variables de tipo booleanas, su valor puede ser unicamente `true` o `false`, y `false` sera el valor por defecto. ( ***No puede convertir el tipos variables entre números y booleanos!*** ) +``` + // ejemplo de código + var isActive bool // variable global + var enabled, disabled = true, false // omitimos el tipo de las variables + func test() { + var available bool // variable local + valid := false // definición breve de una variable + available = true // asignación de un valor a una variable + } +``` +### Tipos Numéricos + +Tipos enteros incluyendo los enteros con signo y sin signo. Para esto Go tiene `int` y `uint` al mismo tiempo, ellos tienen la misma longitud, pero la longitud específica va a depender de su sistema operativo. Ellos utilizan 32-bits en sistemas operativos de 32-bits, y 64-bits en sistemas operativos de 64-bits. Go también tiene tipos que tienen una longitud específica incluyendo `rune`, `int8`, `int16`, `int32`, `int64`, `byte`, `uint8`, `uint16`, `uint32`, `uint64`. Tenga en cuenta que `rune` es el alias de `int32` y `byte` es el alias para `uint8`. + +Algo importante que debes saber es que no puede asignar valores entre estos tipos, esta operación va a causar un error de compilación. +``` + var a int8 + + var b int32 + + c := a + b +``` +Aunque int32 tiene una longitud mayor que uint8, y tiene la misma longitud que int, pero no podrá asignar valores entre ellos. ( ***aquí c afirmará ser de tipo `int`*** ) + +Los tipos Float tienen `float32` y `float64`, y no tienen un tipo llamado `float`, este último solo es el tipo por defecto cuando se utiliza la declaración breve. + +¿Eso es todo? ¡No! Go también tiene número complejos. `complex128` (con una parte de 64-bits reales y otra parte de 64-bits imaginarios) es el tipo por defecto, si necesitas un tipo mas pequeño, hay uno llamado `complex64` (con una parte de 32-bits reales y otra de 32-bits imaginarios). Su forma es `RE+IMi`, donde `RE` es la parte real e `IM` es la parte imaginaria, el último `i` es el número imaginario. Este es un ejemplo de número complejo. +``` + var c complex64 = 5+5i + //salida: (5+5i) + fmt.Printf("Value is: %v", c) +``` +### Cadenas + +Acabamos de hablar sobre que Go utiliza el juego de caracteres de UTF-8. Las Strings son representadas mediante comillas dobles `""` o comillas simples ``` `` ```. +``` + // ejemplo de código + var frenchHello string // forma básica de definir una cadena + var emptyString string = "" // definimos una cadena con un valor vacío + func test() { + no, yes, maybe := "no", "yes", "maybe" // declaración breve + japaneseHello := "Ohaiou" + frenchHello = "Bonjour" // forma básica para asignar un valor + } +``` +Es imposible cambiar el valor de una cadena por su índice. Vas a obtener un error cuando compiles el siguiente código. +``` + var s string = "hello" + s[0] = 'c' +``` +Pero, y si ¿realmente deseo cambiar el valor de un solo carácter de una string? Intenta utilizar el siguiente código. +``` + s := "hello" + c := []byte(s) // convertimos una string a un tipo []byte + c[0] = 'c' + s2 := string(c) // volvemos a convertirlo a un tipo string + fmt.Printf("%s\n", s2) +``` +Podemos utilizar el operador `+` para combinar dos cadenas. +``` + s := "hello," + m := " world" + a := s + m + fmt.Printf("%s\n", a) +``` +y también. +``` + s := "hello" + s = "c" + s[1:] // no puede cambiar los valores de una cadena por su índices pero puedes obtener sus valores. + fmt.Printf("%s\n", s) +``` +Que pasa si ¿quiero tener una string de varias líneas? +``` + m := `hello + world` +``` +` `` ` capturará todos los caracteres en una cadena. + +### Tipos de Errores + +Go tiene un tipo de `error` para hacer frente a los mensajes de error. También tiene un paquete llamado `errors` para manejar los errores. +``` + err := errors.New("emit macho dwarf: elf header corrupted") + if err != nil { + fmt.Print(err) + } +``` +### Estructura de datos fundamental + +La siguiente imagen viene de un artículo sobre [Estructuras de datos en Go](http://research.swtch.com/godata) en [El Blog de Russ Cox](http://research.swtch.com/). Como puedes ver, Go nos da bloques de memoria para almacenar datos. + +![](images/2.2.basic.png?raw=true) + +Imagen 2.1 Go estructura de datos fundamental + +## Algunas facilidades + +### Definir por grupo + +Si deseas definir múltiples constantes, variables o importaciones de paquetes, podemos utilizar formularios de grupos. + +Forma básica. +``` + import "fmt" + import "os" + + const i = 100 + const pi = 3.1415 + const prefix = "Go_" + + var i int + var pi float32 + var prefix string +``` +Forma de grupo. +``` + import( + "fmt" + "os" + ) + + const( + i = 100 + pi = 3.1415 + prefix = "Go_" + ) + + var( + i int + pi float32 + prefix string + ) +``` +A menos que asignes el valor de la constante `iota`, el primer valor de la constante en el grupo `const()` va a ser `0`. Si a las siguientes constantes no se les asignan valores explícitamente, sus valores serán el mismo que el último. Si el último valor de la constante es `iota`, los valores de las siguientes constantes que no son asignadas también serán `iota`. + +### Enumerar con iota + +Go tiene la palabra reservada `iota`, esta palabra reservada va a crear un `enum`, que va a comenzar con `0`, y se va a ir incrementando en `1`. +``` + const( + x = iota // x == 0 + y = iota // y == 1 + z = iota // z == 2 + w // Si no hay una expresión después del nombre de la contasnte, se usa la última expresión, + //entonces aquí nos dice que w = iota implícitamente. Por lo tanto w == 3, e y y x los dos, podrían omitir "= iota" de la misma forma. + ) + + const v = iota // cuando se vuelve a utilizar la palabra reservada `const` seguida de la asignación iota nuevamente, este reinicia su valor a `0`, por esto v = 0. + + const ( + e, f, g = iota, iota, iota // e=0,f=0,g=0 mismos valores de iota en una línea. + ) +``` +### Algunas reglas + +La razón por la que Go es conciso es porque tiene algunos comportamientos por defecto. + +- Cualquier variables que comience con mayúscula se va exportar como pública, de otra manera será una variable privada. +- Se usa la misma regla para funciones y constantes, no existen las palabras reservadas `public` o `private` en Go. + +## Arreglos, segmentos, mapas + +### Arreglos (array) + +`array` es un arreglo obviamente, lo definimos de la siguiente forma. +``` + var arr [n]type +``` +en `[n]type`, `n` es la longitud del arreglo, `type` es el tipo de elementos que contiene. Como en otros lenguajes, utilizamos `[]` para obtener o establecer los elementos en el array. +``` + var arr [10]int // un arraglo de tipo int + arr[0] = 42 // los arreglos arrancan su índice en 0 + arr[1] = 13 // asignamos un valor a un elemento + fmt.Printf("El primer elemento es %d\n", arr[0]) + // obtenemos el valor del elemento, este nos devolvera 42 + fmt.Printf("El último elemento es %d\n", arr[9]) + //este nos devolverá el valor por defecto del elemento número 10 en este array, que es 0 en este caso. +``` +Debido a que la longitud es una parte del tipo del arreglo, `[3]int` y `[4]int` son diferentes tipos, por lo que no podemos cambiar la longitud del arreglos. Cuando utilizamos arreglos como argumentos, las funciones reciben copias en lugar de referencias a ellos! Si lo que busca es utilizar referencias, es posible que desee utilizar `slice` de las que vamos a hablar más adelante. + +Es posible usar `:=` cuando definimos arrays. +``` + a := [3]int{1, 2, 3} // define un arreglo de int con 3 elementos + + b := [10]int{1, 2, 3} + // define arreglo de int con 10 elementos, y los tres primeros son asignados + // el resto utiliza el valor por defecto 0. + + c := [...]int{4, 5, 6} // usamos `…` para reemplazar el número de la longitud, Go la va a calcular por ti. +``` +Es posible que desee utilizar arreglos como elementos de un arreglo, vamos a ver como hacerlo. +``` + // definimos un arreglo de dos dimensiones con dos elementos, y cada elemento tiene cuatro elementos. + arregloDoble := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} + + // podemos escribirlo de una forma más corta. + arregloSimple := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} +``` +Arreglo: estructuras de datos fundamentales. + +![](images/2.2.array.png?raw=true) + +Imagen 2.2 Relación de mapeo de arreglo multi-dimensional + +### Segmentos (slice) + +En muchas situaciones, un arreglo no es una buena elección. Por ejemplo, nosotros no sabemos cual va a ser el largo del arreglo cuando lo definimos, entonces vamos a necesitar un "arreglo dinámico". Este es llamado `slice` en Go. + +Un `segmento` no es realmente un `arreglo dinámico`, es un tipo referenciado. Un `segmento` apunta a un `arreglo` subyacente, su declaración es similar a los `arreglos`, pero no necesitan una longitud. +``` + // al igual que como definimos un arreglo, pero no le pasamos la longitud esta vez + var fsegmento []int +``` +Entonces definimos un `segmento`, e inicializamos sus datos. +``` + segmento := []byte {'a', 'b', 'c', 'd'} +``` +Un `segmento` puede ser re-definido desde otro slice o array existente. Un `segmento` usa `arreglo[i:j]` para tomar sus valores, donde `i` es +el índice del comienzo y `j` es el índice del final, note que `arreglo[j]` no será segmentado, porque la longitud +del segmento es `j-i`. +``` + // definimos un segmento con 10 elementos donde el tipo es byte + var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + + // definimos dos segmento de tipo []byte + var a, b []byte + + // a apunta a los elementos del tercero al quinto elemento en el array ar. + a = ar[2:5] + // ahora tiene los elementos ar[2],ar[3] y ar[4] + + // b es otro slice del array ar + b = ar[3:5] + // ahora b tiene los elementos ar[3] y ar[4] +``` +Note la diferencia entre `segmento` y `arreglo` cuando los definimos. Nosotros usamos `[…]` para que Go +calcule la longitud pero usamos`[]` para definir unicamente un segmento. + +La estructura de datos subyacente a un segmento. + +![](images/2.2.slice.png?raw=true) + +Imagen 2.3 Similitud entre slice y array + +Los segmentos tienen algunas operaciones utiles. + +- Un `segmento` comienza en 0, `ar[:n]` es igual a `ar[0:n]` +- El segundo índice sera la longitud del `slice` si lo omitimos, `ar[n:]` será igual a `ar[n:len(ar)]`. +- Se puede usar `ar[:]` para tomar todo el arreglo, la razón de esto se explica en las dos anteriores explicaciones. + +Más ejemplos acerca de `segmentos` +``` + // definimos un arreglo + var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + // definimos dos segmentos + var aSlice, bSlice []byte + + // Algunas operaciones útiles + aSlice = array[:3] // es igual a aSlice = array[0:3] aSlice tiene los elementos a,b,c + aSlice = array[5:] // es igual a aSlice = array[5:10] aSlice tiene los elementos f,g,h,i,j + aSlice = array[:] // es igual a aSlice = array[0:10] aSlice tiene todos los elementos + + // segmento desde segmento + aSlice = array[3:7] // aSlice tiene los elementos d,e,f,g,len=4,cap=7 + bSlice = aSlice[1:3] // bSlice contiene aSlice[1], aSlice[2], entonces este tendrá los elementos e,f + bSlice = aSlice[:3] // bSlice contiene aSlice[0], aSlice[1], aSlice[2], entonces este tiene d,e,f + bSlice = aSlice[0:5] // bSlice se puede expandir, ahora bSlice contiene d,e,f,g,h + bSlice = aSlice[:] // bSlice tiene los mismos elementos que aSlice, que son d,e,f,g +``` +Un `segmento` es un tipo de referencia, por lo que si uno se modifica entonces afectará al resto. +Por ejemplo, con los elementos de anteriores `aSlice` y `bSlice`, si se modifica el valor de algún elemento en `aSlice`, +`bSlice` será modificado también. + +`slice` por definición es como una estructura, y contiene tres partes. + +- Un puntero que apunta donde comienza el `segmento`. +- la longitud del `segmento`. +- Capacidad, la longitud de donde comienza el indice hacia donde termina el indice del `slice`. +``` + Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + Slice_a := Array_a[2:5] +``` +Los fundamentos de la estructura de datos del código anterior es el siguiente. + +![](images/2.2.slice2.png?raw=true) + +Imagen 2.4 Array información del slice + +Hay algunas funciones integradas para los segmentos. + +- `len` nos devuelve la longitud del `segmento`. +- `cap` nos devuelve la longitud máxima del `segmento` +- `append` añade uno o mas elementos al `segmento`, y nos devuelve el `segmento` . +- `copy` copia elementos de un segmento hacia otro, y nos devuelve el número de elementos que fueron copiados. + +Atención: `append` va a cambiar el array al que apunta el `slice`, y afectará a los otros que apuntan al mismo array. +Ademas si no tiene una longitud suficiente para el slice (`(cap-len) == 0`), `append` nos va a devolver un nuevo array para este slice, en este punto, +los otros slices van a apuntan al anterior array nos serán afectados. + +### Mapas (map) + +Un `mapa` es como un diccionario en Python, lo usamos de la siguiente forma `mapa[claveTipo]tipoValor` para definirlo. + +Vamos a ver algo de código, para configurar o tomar un valor de un `mapa` es como en un `segmento`, usamos la `llave` para ello, pero el indice en un `segmento` puede ser +solo de tipo entero, y en un `mapa` podemos usar mucho mas que eso, `entero`, `cadena`, o lo que quieras. Ademas, todos ellos +pueden utilizar `==` y `!=` para comparar valores. +``` + // usamos string tipo de llave (key), int como el tipo de valor, y debemos usar `make` para inicializarlo. + var numbers map[string] int + // otra forma de definir un mapa + numbers := make(map[string]int) + numbers["one"] = 1 // asignamos el valor para la clave + numbers["ten"] = 10 + numbers["three"] = 3 + + fmt.Println("El tercer número es: ", numbers["three"]) // tomamos el valor + // Esto imprime: El tercer número es: 3 +``` +Algunas cosas a tener en cuenta cuando usamos map. + +- Un `mapa` está y es desordenado, cada vez que imprimamos un `mapa` vamos a obtener diferentes resultados. Es imposible obtener un valor por índice, debe usar la clave. +- Un `mapa` no tiene una longitud fija, es un tipo de referencia al igual que los `segmentos`. +- `len` también funciona con `mapas`, este devuelve el número de claves que tiene el map. +- Es muy sencillo modificar el valor de un elemento del `mapa`, simplemente usamos `numbers["one"]=11` para cambiar el valor que contiene la clave one a `11`. + +Se puede usar la forma `clave:valor` para inicializar los valores del map, y `map` tiene internamente los métodos para verificar si la clave existe. + +Utilice `delete` para borrar un elemento del `mapa`. +``` + // Inicialice un mapa + rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 } + // map nos devuelve dos valores. El segundo valor ,ok es false, si la clave no + //existe true de otra forma. + csharpRating, ok := rating["C#"] + if ok { + fmt.Println("C# se encuentra en el map y su ranking es ", csharpRating) + } else { + fmt.Println("No tenemos un ranking asociado con C# en este map") + } + + delete(rating, "C") // borramos el elemento con la clave "c" +``` +Como se dijo anteriormente, `map` es un tipo por referencia, si dos `map`s apuntan a los mismos datos, +cualquier cambio van a afectar a ambos. +``` + m := make(map[string]string) + m["Hello"] = "Bonjour" + m1 := m + m1["Hello"] = "Salut" // ahora el valor de m["hello"] es Salut +``` +### make, new + +`make` realiza la asignación de memoria para construir las estructuras como los `mapas`, `segmentos`, o `canales`, `new` +es para la reserva de memoria de cada tipo. + +`new(T)` reservamos la memoria para el valor vacío del tipo `T`'s, devuelve la dirección de memoria, que es el valor del tipo `*T`. En los términos de Go, +este devuelve un puntero, que apunta al valor vacío del tipo `T`. + +`new` devuelve punteros. + +La función incorporada `make(T, args)` tiene un diferente efecto que `new(T)`, `make` puede ser usado para `segmentos`, `mapas`, +y `canales`, y nos devuelve un valor inicializado con valor inicial del tipo `T`. La razón para hacer esto es porque estos tres tipos +por debajo deben ser inicializados antes que los punteros a ellos. Por ejemplo, un `segmento` contiene punteros que por debajo apuntan a +un `arreglo`, longitud y capacidad. Antes de que esta información sea inicializada, un `segmento` es `nil`, entonces para un `segmento`, `mapa`y +`canal`, `make` inicializa los datos que tienen por debajo, y asigna algunos valores configurables. + +`make` devuelve valores distinto de cero. + +La siguiente imagen nos muestra como son diferentes `new` y `make`. + +![](images/2.2.makenew.png?raw=true) + +Imagen 2.5 Reserva de memoria de fondo de make y new + +En cuando a valor cero, no significa valor vacío. Es el valor de las variables cuando no son asignados de forma manual, usualmente es 0, esta es la lista de algunos de estos valores. +``` + int 0 + int8 0 + int32 0 + int64 0 + uint 0x0 + rune 0 // el tipo actual de rune es int32 + byte 0x0 // el tipo actual de byte es uint8 + float32 0 // la longitud es 4 byte + float64 0 // la longitud es 8 byte + bool false + string "" +``` +## Enlaces + +- [Indice](preface.md) +- Sección anterior: ["Hello, Go"](02.1.md) +- Siguiente sección: [Sentencias de control y funciones](02.3.md) diff --git a/es/02.3.md b/es/02.3.md new file mode 100644 index 00000000..c8954c99 --- /dev/null +++ b/es/02.3.md @@ -0,0 +1,516 @@ +# 2.3 Sentencias de control y funciones + +En esta sección, vamos a hablar sobre sentencias de control de flujo y operaciones con funciones en Go. + +## Sentencias de control + +El mayor invento en los lenguajes de programación es el control de flujo. Gracias a ellos, podemos utilizar algunas sentencias sencillas de control para representar lógicas complejas. Tenemos tres categorías, condicionales, controles de ciclos y saltos incondicionales. + +### if + +`if` es la más común de las palabras reservadas en sus programas. Si se cumplen las condiciones entonces realiza algo, si no realiza otra cosa o nada. + +`if` no necesita paréntesis en Go. +``` + if x > 10 { + fmt.Println("x es mayor que 10") + } else { + fmt.Println("x es menor que 10") + } +``` +Lo más útil de `if` en Go es que puede tener una instrucción de inicialización antes de la sentencia de condición. El alcance o ámbito de las variables de inicialización que nosotros definimos en esta condición es unicamente dentro del bloque del `if`. +``` + // inicializamos x, entonces verificamos si x es mayor que 10 + if x := computedValue(); x > 10 { + fmt.Println("x es mayor que 10") + } else { + fmt.Println("x es menor que 10") + } + + // el siguiente código no va a compilar + fmt.Println(x) +``` +Utiliza if-else para múltiples condiciones. +``` + if entero == 3 { + fmt.Println("entero es igual a 3") + } else if entero < 3 { + fmt.Println("entero es menor que 3") + } else { + fmt.Println("entero es mayor que 3") + } +``` +### goto + +Go la palabra reservada `goto`, se cuidadoso cuando la usas. `goto` tiene un salto hacia la `etiqueta` en el cuerpo de el mismo bloque de código. +``` + func myFunc() { + i := 0 + Here: // label termina con ":" + fmt.Println(i) + i++ + goto Here // salta hacia el label "Here" + } +``` +El nombre de la etiqueta diferencia entre mayúsculas y minúsculas. + +### for + +`for` es la lógica de control mas poderosa en Go, puede leer los datos en los ciclos y realizar operaciones iterativas, al igual que un típico `while`. +``` + for expression1; expression2; expression3 { + //... + } +``` +`expression1`, `expression2` y `expression3` son obviamente todas expresiones, donde `expression1` y `expression3` son definiciones de variables o valores de retornos de funciones, y `expression2` es una sentencia condicional. `expression1` se ejecutara siempre antes de cada bucle, y `expression3` después. + +Un ejemplo es mas útil que cientos de palabras. +``` + package main + import "fmt" + + func main(){ + sum := 0; + for index:=0; index < 10 ; index++ { + sum += index + } + fmt.Println("la suma es igual a ", sum) + } + // Print:sum es igual a 45 +``` +A veces necesitamos asignaciones múltiples, Go tiene el operador `,`, así que podemos usar la asignación paralela como `i, j = i + 1, j - 1`. + +Si no son necesarios, podemos omitir a `expression1` y `expression3`. +``` + sum := 1 + for ; sum < 1000; { + sum += sum + } +``` +Podemos omitir también el `;`. ¿Se siente familiar? Si, es un `while`. +``` + sum := 1 + for sum < 1000 { + sum += sum + } +``` +Hay dos operaciones importantes en los ciclos que son `break` y `continue`. `break` salta afuera del ciclo, y `continue` salta el ciclo actual y continua en el siguiente. Si usted anida ciclos, utilice `break` para saltar al bucle que esta junto. +``` + for index := 10; index>0; index-- { + if index == 5{ + break // o continue + } + fmt.Println(index) + } + // break imprime 10、9、8、7、6 + // continue imprime 10、9、8、7、6、4、3、2、1 + +`for` podría leer los datos desde un `segmento` o un `mapa` cuando es utilizado con `range`. + + for k,v:=range map { + fmt.Println("llave del mapa:",k) + fmt.Println("valor del mapa:",v) + } +``` +Como Go soporta el retorno de valores múltiples y nos da errores de compilación cuando utiliza valores que no fueron definidos, por eso puede necesitar utilizar `_` para descartar algunos valores de retorno. +``` + for _, v := range map{ + fmt.Println("valor del mapa:", v) + } +``` +### switch + +A veces puedes pensar que estas utilizando demasiados valores `if-else` para implementar alguna lógica, también puedes pensar que no se ve bien y que no sea correcto para mantener a futuro. Ahora es tiempo para utilizar `switch` para resolver este problema. +``` + switch sExpr { + case expr1: + algunas instrucciones + case expr2: + algunas otras instrucciones + case expr3: + algunas otras instrucciones + default: + otro código + } +``` +El tipo de `sExpr`, `expr1`, `expr2`, y `expr3` debe ser el mismo. `switch` es muy flexible, las condiciones no necesitan ser constantes, es ejecutado de arriba hacia abajo hasta que se cumpla alguna condición. Si no hay ninguna declaración después de la palabra reservada `switch`, entonces este se compara con `true`. +``` + i := 10 + switch i { + case 1: + fmt.Println("i es igual a 1") + case 2, 3, 4: + fmt.Println("i es igual a 2, 3 o 4") + case 10: + fmt.Println("i es igual a 10") + default: + fmt.Println("Todo lo que yo se, es que i es un entero") + } +``` +En la quinta linea, pusimos muchos valores en un solo `case`, y no necesitamos utilizar `break` en el final del cuerpo de un `case`. Saltara fuera del cuerpo de switch una vez que coincida con algún case y ejecute las instrucciones dentro del mismo. Si usted busca que siga comparando con otros cases, va a necesitar utilizar la palabra reservada `fallthrough`. +``` + integer := 6 + switch integer { + case 4: + fmt.Println("integer <= 4") + fallthrough + case 5: + fmt.Println("integer <= 5") + fallthrough + case 6: + fmt.Println("integer <= 6") + fallthrough + case 7: + fmt.Println("integer <= 7") + fallthrough + case 8: + fmt.Println("integer <= 8") + fallthrough + default: + fmt.Println("default case") + } +``` +Este programa va a imprimir la siguiente información. +``` + integer <= 6 + integer <= 7 + integer <= 8 + default case +``` +## Funciones + +Utilizamos la palabra reservada `func` para definir funciones. +``` + func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { + // cuerpo de la función + // retorna múltiples valores + return value1, value2 + } +``` +Podemos obtener la siguiente información del ejemplo anterior. + +- Utilizamos la palabra reservada `func` para definir la función llamada `funcName`. +- Funciones tienen cero, uno o mas de un argumento, el tipo del argumento después del nombre del mismo y separados por `,`. +- Las funciones pueden devolver múltiples valores. +- El ejemplo iene dos valores de retorno llamados `output1` y `output2`, se pueden omitir los nombre y utilizar unicamente los tipos. +- Si solo hay un valor de retorno y omite el nombre, no va a necesitar comas para retornar mas valores. +- Si la función no tiene valores de retorno, puede omitir la parte de retorno. +- Si la función tiene valores de retorno, va a necesitar utilizar `return` en alguna parte del cuerpo de la función. + +Veamos un ejemplo práctico. (calcular el valor mínimo) +``` + package main + import "fmt" + + // devolvemos el valor mas grande entre a y b + func max(a, b int) int { + if a > b { + return a + } + return b + } + + func main() { + x := 3 + y := 4 + z := 5 + + max_xy := max(x, y) // llama a la función max(x, y) + max_xz := max(x, z) // llama a la función max(x, z) + + fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) + fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) + fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // llamamos a la función aquí + } +``` +En el ejemplo anterior, tenemos dos argumentos en la función `max`, los de tipo `int`, por eso el primer tipo puede ser omitido, como `a, b int` en lugar de `a int, b int`. Se cumple la misma regla para mas argumentos. Nótese que `max` tiene solo un valor de retorno, por lo que solo escribimos el tipo de valor de retorno, esta es una forma corta. + +### Retorno de múltiples valores + +Una de las cosas en las que Go es mejor que C es que soporta el retorno de múltiples valores. + +Vamos a utilizar el ejemplo anterior aquí. +``` + package main + import "fmt" + + // retorna el resultado de A + B y A * B + func SumAndProduct(A, B int) (int, int) { + return A+B, A*B + } + + func main() { + x := 3 + y := 4 + + xPLUSy, xTIMESy := SumAndProduct(x, y) + + fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) + fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) + } +``` +En el ejemplo anterior devolvemos dos valores sin nombre, y tu también puedes nombrarlos. Si nombramos los valores de retorno, solo vamos a utilizar `return` para devolver para devolver bien los valores ya que se inicializan en la función automáticamente. Debes tener en cuenta que si tus funciones se van a utilizar fuera del paquete, lo que significa que los nombre de las funciones inician con mayúsculas, es mejor que escriba la sentencia mas completa para `return`; esto hace que es código sea mas legible. +``` + func SumAndProduct(A, B int) (add int, Multiplied int) { + add = A+B + Multiplied = A*B + return + } +``` +### Argumentos variables + +Go soporta funciones con un número variable de argumentos. Estas funciones son llamadas "variadic", lo que significa que puede darle un numero incierto de argumentos a la función. +``` + func myfunc(arg ...int) {} +``` +`arg …int` le dice a Go que esta función tiene argumentos variables. Ten en cuenta que estos argumentos son de tipo `int`. En el cuerpo de la función , `arg` será un `segmento` de `enteros`. +``` + for _, n := range arg { + fmt.Printf("Y el número es: %d\n", n) + } +``` +### Pasando argumentos por valor y punteros + +Cuando pasamos argumentos a una función a la cual llamamos, esa función actualmente toma una copia de nuestra variables, cualquier cambio que realicemos no va a afectar a la variable original. + +Vamos a ver un ejemplo para probar lo que decimos. +``` + package main + import "fmt" + + // función sencilla para sumar 1 a 'a' + func add1(a int) int { + a = a+1 // cambiamos el valor de a + return a // devolvemos el nuevo valor de a + } + + func main() { + x := 3 + + fmt.Println("x = ", x) // debe imprimir "x = 3" + + x1 := add1(x) // llamamos a add1(x) + + fmt.Println("x+1 = ", x1) // debe imprimir "x+1 = 4" + fmt.Println("x = ", x) // debe imprimir "x = 3" + } +``` +¿Viste eso? Siempre que llamamos a `add1`, y `add1` le suma uno a `a`, el valor de `x` no sufre cambios. + +El motivo de esto es muy sencillo: cuando llamamos a `add1`, nosotros obtenemos una copia de `x` para esto, no `x` en si mismo. + +Ahora nos debemos preguntar, como puedo pasar el verdadero valor de `x` a la función. + +Para eso necesitamos utilizar punteros. Sabemos que las variables son almacenadas en memoria, y todas ellas tienen una dirección de memoria, nosotros vamos a cambiar el valor de esa variables si cambiamos los valores en la dirección de memoria de esa variable. Por lo tanto la función `add1` tiene que saber la dirección de memoria de `x` para poder cambiar su valor. Por eso necesitamos pasar de la siguiente forma su valor `&x` a la función, y cambiar el tipo de argumento a uno de tipo puntero `*int`. Tiene que ser consciente de que pasamos una copia del puntero, no una copia del valor. +``` + package main + import "fmt" + + // función sencilla para sumar 1 a a + func add1(a *int) int { + *a = *a+1 // cambiamos el valor de a + return *a // devolvemos el nuevo valor de a + } + + func main() { + x := 3 + + fmt.Println("x = ", x) // debe imprimir "x = 3" + + x1 := add1(&x) // llamamos a add1(&x) pasando la dirección de memoria de x + + fmt.Println("x+1 = ", x1) // debe imprimir "x+1 = 4" + fmt.Println("x = ", x) // debe imprimir "x = 4" + } +``` +Ahora podemos cambiar el valor de `x` en la función. ¿Por qué usamos punteros? ¿Cuál es la ventaja? + +- Usamos mas funciones que modifiquen una misma variable. +- Tiene un bajo costo utilizar direcciones de memoria (8 bytes), la copia no es una forma eficiente tanto en el tiempo como en el espacio para pasar variables. +- Las `cadenas`, `segmentos` y `mapas` son tipos de referenciados, por eso ellos por defecto utilizan punteros cuando se pasan a funciones. (Atención: si necesitas cambiar el tamaño de un `segmento`, vas a necesitar pasar el puntero de forma explicita) + +### defer + +Go tiene una palabra reservada muy bien diseñada llamada `defer`, puedes tener muchas declaraciones de `defer` en una función; ellas se ejecutan en orden inverso cuando el programa ejecuta el final de la función. Es útil especialmente cuando el programa abre un recurso como un archivo, estos archivos tienen que ser cerrados antes de que la función devuelva un error. Vamos a ver algún ejemplo. +``` + func ReadWrite() bool { + file.Open("file") + // realizamos alguna tarea + if failureX { + file.Close() + return false + } + + if failureY { + file.Close() + return false + } + + file.Close() + return true + } +``` +Vimos la repetición de código en varias ocasiones, `defer` nos va a resolver muy bien este problema. No solo nos va a ayudar a realizar código limpio, y también lo va a hacer mas legible. +``` + func ReadWrite() bool { + file.Open("file") + defer file.Close() + if failureX { + return false + } + if failureY { + return false + } + return true + } +``` +Si hay mas de un `defer`, ellos se van a ejecutar en orden inverso. El siguiente ejemplo va a imprimir `4 3 2 1 0`. +``` + for i := 0; i < 5; i++ { + defer fmt.Printf("%d ", i) + } +``` +### Funciones como valores y tipos + +Las funciones son también variables en Go, podemos usar un `type` para definirlas. Las funciones que tienen la misma firma pueden verso como del mismo tipo. +``` + type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...]) +``` +¿Cuál es la ventaja de esta característica? La respuesta es que podemos pasar funciones como valores. +``` + package main + import "fmt" + + type testInt func(int) bool // definimos un función como un tipo de variable + + func isOdd(integer int) bool { + if integer%2 == 0 { + return false + } + return true + } + + func isEven(integer int) bool { + if integer%2 == 0 { + return true + } + return false + } + + // pasamos la función `f` como un argumento de otra función + + func filter(slice []int, f testInt) []int { + var result []int + for _, value := range slice { + if f(value) { + result = append(result, value) + } + } + return result + } + + func main(){ + slice := []int {1, 2, 3, 4, 5, 7} + fmt.Println("slice = ", slice) + odd := filter(slice, isOdd) // usamos la función como un valor + fmt.Println("Odd elements of slice are: ", odd) + even := filter(slice, isEven) + fmt.Println("Even elements of slice are: ", even) + } +``` +Es muy útil cuando usamos interfaces. Como puedes ver `testInt` es una variable que tiene como tipo una función, y devuelve valores y el argumento de `filter` es el mismo que el de `testInt`. Por lo tanto, tenemos una lógica mas compleja en nuestro programa, y hacemos nuestro código mas flexible. + +### Panic y Recover + +Go no tiene estructura `try-catch` como lo tiene Java. En vez de lanzar excepciones, Go usa `panic` y `recover` para hacer frente a los errores. Sin embargo, no debería usar mucho `panic`, aunque sea muy poderoso. + +Panic es una función incorporada para romper el flujo normal del programa y entrar en un estado de pánico. Cuando la función `F` llama a `panic`, la función `F` no continuara ejecutándose, pero sus funciones `defer` siempre se ejecutaran. Entonces `F` vuelve a su punto de ruptura donde se causo el estado de pánico. El programa no va a finalizar hasta que todas las funciones retornen con `panic` hasta el primer nivel de esa `goroutine`. `panic` se puede producir con una llamada a `panic` en el programa, y algunos errores pueden causar una llamada a `panic` como un intento de acceso fuera de un array. + +Recover es una función incorporada para recuperar una `goroutine` de un estado de pánico, solo el llamado a `recover` es útil en una función `defer` porque las funciones normales no van a ser ejecutadas cuando el programa se encuentre en un estado de pánico. Vamos a poder atrapar el valor de `panic` si el programa se encuentra en un estado de pánico, este nos va a devolver `nil` si el programa se encuentra en un estado normal. + +El siguiente ejemplo nos muestra como utilizar `panic`. +``` + var user = os.Getenv("USER") + + func init() { + if user == "" { + panic("no hay valor para $USER") + } + } +``` +El siguiente ejemplo nos muestra como verificar un `panic`. +``` + func throwsPanic(f func()) (b bool) { + defer func() { + if x := recover(); x != nil { + b = true + } + }() + f() // si f causa un panic, este va a recuperase (recover) + return + } +``` +### La función `main` y la función `init` + +Go tiene dos retenciones (retention) que son llamadas `main` e `init`, donde `init` puede ser usada en todos los paquetes y `main` solo puede ser usada en el paquete `main`. Estas dos funciones no son capaces de tener argumentos o valores de retorno. A pesar de que podemos escribir muchas funciones `init` en un mismo paquete, yo recomiendo fuertemente que escriban solo una función `init` por cada paquete. + +Los programas en Go van a llamar a `init()` y a `main()` automáticamente, así que no es necesario llamarlas. Para cada paquete, la función `init` es opcional, pero `package main` tiene una y solo una función `main`. + +Los programas se inicializan y se ejecutan desde el paquete `main`, si el paquete `main` importa otros paquetes, ellos serán importados en tiempo de compilación. Si un paquete es importado muchas veces, este va a ser compilado solo una vez. Después de importar los paquetes, el programa va a inicializar las constantes y variables en los paquetes importados, luego va a ejecutar la función `init` si es que existe, y así sucesivamente. Después de que todos los paquetes fueran inicializados, el programa va a comenzar a inicializar las constantes y variables en el paquete `main`, entonces va a ejecutar la función `init` en el paquete si es que existe. La siguiente figura les va a mostrar el proceso. + +![](images/2.3.init.png?raw=true) + +Figure 2.6 Flujo de inicialización de un programa en Go + +### import + +Nosotros usamos `import` muy frecuentemente en los programas en Go como se muestra acá. +``` + import( + "fmt" + ) +``` +Entonces nosotros podemos usar las funciones de ese paquete de la siguiente forma. + + fmt.Println("Hola mundo") + +`fmt` es parte de la librería estándar de Go , esta localizado en $GOROOT/pkg. Go utiliza las siguiente dos formas para paquete de terceros. + +1. Path relativo + import "./model" // carga el paquete que se encuentra en el mismo directorio, yo no recomiendo utilizar esta forma. +2. Path absoluto + import "shorturl/model" // carga un paquete en el path "$GOPATH/pkg/shorturl/model" + +Tenemos algunos operadores especiales para importar paquetes, y los principiantes normalmente se confunden con estos operadores. + +1. EL operador punto . + A veces podemos ver personas que utilizan la siguiente forma para importar paquetes. + + import( + . "fmt" + ) + + El operador punto significa que podemos omitir el nombre del paquete cuando llamamos a las funciones del mismo. En vez de esto `fmt.Printf("Hola mundo")` ahora podemos usar esto `Printf("Hola mundo")`. +2. El operador alias. + Este puede cambiar el nombre del paquete que vamos a importar cuando llamamos a las funciones del mismo. + + import( + f "fmt" + ) + + En vez de esto `fmt.Printf("Hola mundo")` ahora podemos usar esto `f.Printf("Hola mundo")`. +3. El operador `_`. + Este es un operador un poco difícil de comprender si no tenemos una breve explicación. + + import ( + "database/sql" + _ "github.com/ziutek/mymysql/godrv" + ) + + El operador `_` en realidad significa que solo importamos ese paquete, y usamos la función `init` de ese paquete, y no estamos seguros si vamos a utilizar funciones de dicho paquete. + +## Enlaces + +- [Índice](preface.md) +- Sección anterior: [Principios de Go](02.2.md) +- Siguiente sección: [struct](02.4.md) diff --git a/es/02.4.md b/es/02.4.md new file mode 100644 index 00000000..1274079e --- /dev/null +++ b/es/02.4.md @@ -0,0 +1,214 @@ +# 2.4 struct + +## struct + +Podemos definir en Go nuevos tipos de contenedores con otras propiedades o campos como en otros lenguajes de programación. Por ejemplo, podemos crear el tipo llamado `persona` para representar una persona, este tipo tiene nombre y edad. Podemos llamar estos tipos de tipos como `struct`. +``` + type persona struct { + nombre string + edad int + } +``` +Mira que fácil es definir un `struct`! + +Tiene dos campos. + +- `nombre` es una `string` usada para guardar el nombre de personas. +- `edad` es un `int` usado para guardar la de edad de personas. + +Vamos a ver como usarlo. +``` + type persona struct { + nombre string + edad int + } +``` + var P persona // p es de tipo persona + + P.nombre = "Astaxie" // asigna "Astaxie" al campo 'nombre' de p + P.edad = 25 // asigna 25 al campo 'edad' de p + fmt.Printf("El nombre de la persona es %s\n", P.name) // accedemos al campo 'nombre' de p + +Tenemos tres formas mas de definir un struct. + +- Asignando un valor inicial en forma ordenada + + P := persona{"Tom", 25} + +- Usando el formato `campo:valor` para inicializarlo sin orden + + P := persona{edad:24, nombre:"Bob"} + +- Definimos una struct anónima, y la inicializamos + + P := struct{nombre string; edad int}{"Amy",18} + +Vamos a ver un ejemplo completo. +``` + package main + import "fmt" + + // definimos un tipo nuevo + type persona struct { + nombre string + edad int + } + + // comparamos la edad de dos personas, y devolvemos la mas vieja con la + // diferencia, struct es pasado por valor + func Older(p1, p2 persona) (persona, int) { + if p1.edad>p2.edad { + return p1, p1.edad-p2.edad + } + return p2, p2.edad-p1.edad + } + + func main() { + var tom persona + + // inicialización + tom.nombre, tom.edad = "Tom", 18 + + // inicializamos los dos valores con el formato "campo:valor" + bob := persona{edad:25, nombre:"Bob"} + + // inicializamos los dos valores en orden + paul := persona{"Paul", 43} + + tb_Older, tb_diff := Older(tom, bob) + tp_Older, tp_diff := Older(tom, paul) + bp_Older, bp_diff := Older(bob, paul) + + fmt.Printf("De %s y %s, %s es mas viejo por %d años\n", tom.nombre, bob.nombre , tb_Older.nombre , tb_diff) + + fmt.Printf("De %s y %s, %s es mas viejo por %d años\n", tom.nombre, paul.nombre, tp_Older.nombre, tp_diff) + + fmt.Printf("De %s y %s, %s es mas viejo por %d años\n", bob.nombre, paul.nombre, bp_Older.nombre, bp_diff) + } +``` +### Campos incrustados en un struct + +Solo les mostré como definir struct con campos que tienen nombre y tipo. De hecho, Go soporta campos sin nombre pero si con tipo, vamos a llamar a estos campos incrustados. + +Cuando el campo incrustado es un struct, todos los campos de ese struct serán campos del nuevo struct de forma implícita. + +Vamos a ver un ejemplo. +``` + package main + import "fmt" + + type Human struct { + name string + age int + weight int + } + + type Student struct { + Human // campo incrustado, esto significa que el struct Student va a incluir los campos que tiene Human. + speciality string + } + + func main() { + // inicializamos a student + mark := Student{Human{"Mark", 25, 120}, "Computer Science"} + + // campos accesibles + fmt.Println("Su nombre es ", mark.name) + fmt.Println("Su edad es ", mark.age) + fmt.Println("Su peso es ", mark.weight) + fmt.Println("Su especialidad es ", mark.speciality) + // modificamos un campo + mark.speciality = "AI" + fmt.Println("Mark cambio su especialidad") + fmt.Println("Su especialidad es ", mark.speciality) + // modificamos su edad + fmt.Println("Mark esta mas viejo") + mark.age = 46 + fmt.Println("Su edad es ", mark.age) + // modificamos su peso + fmt.Println("Mark ya no es mas un atleta") + mark.weight += 60 + fmt.Println("Su peso es ", mark.weight) + } +``` +![](images/2.4.student_struct.png?raw=true) + +Figure 2.7 Herencia en Student y Human + +Vemos que accedemos a la edad y nombre en Student de la misma forma que lo hacemos con Human. Así es como funcionan los campos incrustados. Es muy útil “cool”, no lo es? Espera, hay algo todavía mas “cool”! Puede utilizar a Student para acceder a los campos incrustados de Human! + + mark.Human = Human{"Marcus", 55, 220} + mark.Human.age -= 1 + +Todos los tipos pueden ser utilizados como campos incrustados. +``` + package main + import "fmt" + + type Skills []string + + type Human struct { + name string + age int + weight int + } + + type Student struct { + Human // struct como un campo incrustado + Skills // string como un campo incrustado + int // usamos un tipo embebido como un campo incrustado + speciality string + } + + func main() { + // inicializamos al Student Jane + jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"} + // accedemos a sus campos + fmt.Println("Su nombre es ", jane.name) + fmt.Println("Su edad es , jane.age) + fmt.Println("Su peso es ", jane.weight) + fmt.Println("Su especialidad es ", jane.speciality) + // modificamos el valor del campo skill + jane.Skills = []string{"anatomy"} + fmt.Println("Sus habilidades son ", jane.Skills) + fmt.Println("Ella adquirió don habilidades mas ") + jane.Skills = append(jane.Skills, "physics", "golang") + fmt.Println("Sus habilidades ahora son ", jane.Skills) + // modificamos un campo embebido + jane.int = 3 + fmt.Println("Su numero preferido es ", jane.int) + } +``` +En el ejemplo anterior, podemos ver que a todos los tipos se les pueden incrustar campos y podemos utilizar funciones para operarlos. + +Hay un problema mas, si Human tiene un campo llamado `phone` y Student tiene otro campo llamado con el mismo nombre, que deberíamos hacer? + +Go utiliza una forma muy sencilla para resolverlo. Los campos exteriores consiguen accesos superiores, lo que significa, es que cuando se accede a `student.phone`, obtendremos el campo llamado phone en student, no en el struct de Human. Esta característica se puede ver simplemente como una `sobrecarga` de campos. +``` + package main + import "fmt" + + type Human struct { + name string + age int + phone string // Human tiene el campo phone + } + + type Employee struct { + Human // campo embebido Human + speciality string + phone string // phone en employee + } + + func main() { + Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} + fmt.Println("El teléfono del trabajo de Bob es:", Bob.phone) + // accedemos al campo phone en Human + fmt.Println("El teléfono personal de Bob es:", Bob.Human.phone) + } +``` +## Enlaces + +- [Índice](preface.md) +- Sección anterior: [Sentencias de control y funciones](02.3.md) +- Siguiente sección: [Orientado a objetos](02.5.md) diff --git a/es/02.5.md b/es/02.5.md new file mode 100644 index 00000000..8ec75202 --- /dev/null +++ b/es/02.5.md @@ -0,0 +1,307 @@ +# Orientado a objetos + +En las ultimas dos secciones hablamos de funciones y struct, alguna vez pensaste en usar funciones como campos de un struct? En esta sección, voy a realizar una introducción a otro tipo de funciones , que vamos a llamar métodos (`method`). + +## Métodos + +Supongamos que definimos un struct del estilo rectángulo(rectangle), y buscas calcular el área, por lo general vamos a usar el siguiente código para lograr este objetivo. +``` + package main + import "fmt" + + type Rectangle struct { + width, height float64 + } + + func area(r Rectangle) float64 { + return r.width*r.height + } + + func main() { + r1 := Rectangle{12, 2} + r2 := Rectangle{9, 4} + fmt.Println("El área de r1 es: ", area(r1)) + fmt.Println("El área de r2 is: ", area(r2)) + } +``` +En el ejemplo de arriba podemos calcular el área de un rectángulo, usamos la función llamada `area`, pero no es un método de un struct “rectangle” (como un método en un clase en un lenguaje orientado a objeto clásico). La función y el struct como se ve son dos cosas independientes. + +Hasta ahora eso no es un problema. Pero que pasa si quiere calcular el área de un circulo, cuadrado, pentágono, u otra figura, vamos a tener que agregar mas funciones con nombres muy similares. + +![](images/2.5.rect_func_without_receiver.png?raw=true) + +Figure 2.8 Relación entre funciones y struct + +Obviamente, no es “cool”. Además el área debería ser propiedad del circulo o el rectángulo. + +Por estas razones, tenemos conceptos sobre `métodos`. `métodos` es afiliado a un tipo, tienen la misma sintaxis que una función excepto por una palabra más después de la palabra reservada `func` que es llamada receptor (`receiver`) que es el cuerpo principal de ese método. + +Utiliza el mismo ejemplo, `Rectangle.area()` pertenece al rectángulo, no como una función periférica. Mas específicamente, `length`, `width` y `area()` todos pertenecen a rectángulo. + +Como dice Rob Pike. + + "Un método es una función con un primer argumento implícito, llamado receiver." + +Sintaxis de un método. +``` + func (r ReceiverType) funcName(parameters) (results) +``` +Cambiemos el ejemplo anterior para utilizar métodos. +``` + package main + import ( + "fmt" + "math" + ) + + type Rectangle struct { + width, height float64 + } + + type Circle struct { + radius float64 + } + + func (r Rectangle) area() float64 { + return r.width*r.height + } + + func (c Circle) area() float64 { + return c.radius * c.radius * math.Pi + } + + func main() { + r1 := Rectangle{12, 2} + r2 := Rectangle{9, 4} + c1 := Circle{10} + c2 := Circle{25} + + fmt.Println("El área de r1 es: ", r1.area()) + fmt.Println("El área de r2 es: ", r2.area()) + fmt.Println("El área de c1 es: ", c1.area()) + fmt.Println("El área de c2 es: ", c2.area()) + } +``` +Notas para el uso de métodos. + +- Si el nombre de un método es el mismo, pero no tiene el mismo receptor, ellos no son iguales. +- Los métodos son capaces de acceder a los campos en los receptores. +- Usamos el `.` para llamar al método en el struct, al igual que cuando llamamos a un campo. + +![](images/2.5.shapes_func_with_receiver_cp.png?raw=true) + +Figure 2.9 Los métodos son diferentes en diferentes struct + +En el ejemplo anterior, el método area() es respectivamente perteneciente a Rectangle y a Circle, por lo que los receptores son Rectangle y Circle. + +Una cosa es importante notar, que un método con una línea de puntos significa que el receptor se pasa por valor, no por referencia. La diferencia entre ellos es que el método podría cambiar el valor del receptor cuando el mismo es pasado por referencia, y este toma una copia del receptor cuando es pasado por valor. + +¿El receptor puede ser unicamente un struct? Por supuesto que no, cualquier tipo podría ser el receptor en un método. Puede ser un poco confuso cuando hablamos de tipos personalizados(“customized type”), struct es un tipo especial de tipo modificado, hay varios tipos personalizado. + +Utilice el siguiente formato para definir un tipo personalizado. +``` + type typeName typeLiteral +``` +Un ejemplo sobre tipos personalizados. +``` + type ages int + + type money float32 + + type months map[string]int + + m := months { + "January":31, + "February":28, + ... + "December":31, + } +``` +Espero que ahora sepa como utilizar los tipos personalizados. Es similar a `typedef` en C, nosotros usamos `ages` para sustituir a `int` en el ejemplo anterior. + +Volvamos a los `métodos`. + +Puedes utilizar tantos métodos en tipos personalizados como desees. +``` + package main + import "fmt" + + const( + WHITE = iota + BLACK + BLUE + RED + YELLOW + ) + + type Color byte + + type Box struct { + width, height, depth float64 + color Color + } + + type BoxList []Box //un slice de boxes + + func (b Box) Volume() float64 { + return b.width * b.height * b.depth + } + + func (b *Box) SetColor(c Color) { + b.color = c + } + + func (bl BoxList) BiggestsColor() Color { + v := 0.00 + k := Color(WHITE) + for _, b := range bl { + if b.Volume() > v { + v = b.Volume() + k = b.color + } + } + return k + } + + func (bl BoxList) PaintItBlack() { + for i, _ := range bl { + bl[i].SetColor(BLACK) + } + } + + func (c Color) String() string { + strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"} + return strings[c] + } + + func main() { + boxes := BoxList { + Box{4, 4, 4, RED}, + Box{10, 10, 1, YELLOW}, + Box{1, 1, 20, BLACK}, + Box{10, 10, 1, BLUE}, + Box{10, 30, 1, WHITE}, + Box{20, 20, 20, YELLOW}, + } + + fmt.Printf("Tenemos %d boxes en nuestro conjunto\n", len(boxes)) + fmt.Println("El volumen de la primera es ", boxes[0].Volume(), "cm³") + fmt.Println("El color de la última es",boxes[len(boxes)-1].color.String()) + fmt.Println("La más grande es", boxes.BiggestsColor().String()) + + fmt.Println("Vamos a pintarlas a todas de negro") + boxes.PaintItBlack() + fmt.Println("El color de la segunda es", boxes[1].color.String()) + + fmt.Println("Obviamente, ahora, la más grande es", boxes.BiggestsColor().String()) + } +``` +Vamos a definir algunas constantes y tipos personalizados. + +- Usamos `Color` como un alias de `byte`. +- Definimos un struct `Box` que tiene los campos height, width, length y color. +- Definimos un struct `BoxList` que tiene `Box` como sus campos. + +Entonces vamos a definir algunos métodos para personalizar nuestros tipos. + +- Volume() usa a Box como su receptor, devuelve el volumen de Box. +- SetColor(c Color) cambiar el color de las Box. +- BiggestsColor() devuelve el colo que tiene la de mayor volumen. +- PaintItBlack() configuramos el color para todas las Box en BoxList a negro. +- String() usamos Color como su receptor, devolvemos un string con formato de el nombre del color. + +¿Es mucho mas claro cuando usamos palabras para describir nuestro requerimientos? Usualmente escribimos nuestros requerimientos antes de comenzar a programar. + +### Utilizar punteros como un receptor + +Vamos a mirar el método `SetColor`, su receptor es un puntero de tipo Box. Si, puedes usar `*Box` como receptor. ¿Por qué usaríamos un puntero aquí? Porque buscamos cambiar el color de Box en este método, si no usamos un puntero, solo cambiaría el valor de la copia de Box. + +Si vemos el receptor como el primer argumento de los métodos, no es difícil de entender como funcionan estos. + +Podría decir que deberíamos usar `*b.Color=c` en vez de `b.Color=c` en el método SetColor(). Cualquiera de los dos estaría bien, porque Go lo sabe interpretar. ¿Ahora crees que Go es mas fascinante? + +También podría decir que deberíamos usar `(&bl[i]).SetColor(BLACK)` en `PaintItBlack` porque le pasamos un puntero a `SetColor`. Una vez más, cualquiera de los dos esta bien, porque ¡Go sabe interpretarlo correctamente! + +### Herencia de métodos + +Aprendimos sobre la herencia de campos en la última sección, también tenemos la herencia de métodos en Go. Así que si un campo anónimo tiene métodos, el struct que contiene el campo tendrá todos los métodos del mismo. +``` + package main + import "fmt" + + type Human struct { + name string + age int + phone string + } + + type Student struct { + Human // campo anónimo + school string + } + + type Employee struct { + Human + company string + } + + // definimos un método en Human + func (h *Human) SayHi() { + fmt.Printf("Hola, Yo soy %s puedes llamarme al %s\n", h.name, h.phone) + } + + func main() { + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} + sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + + mark.SayHi() + sam.SayHi() + } +``` +### Sobrecarga de métodos + +Si queremos que Employee tenga su propio método llamado `SayHi`, podemos definir el método con el mismo nombre que el de Employee, y así ocultaremos `SayHi` de Human cuando lo llamemos. +``` + package main + import "fmt" + + type Human struct { + name string + age int + phone string + } + + type Student struct { + Human + school string + } + + type Employee struct { + Human + company string + } + + func (h *Human) SayHi() { + fmt.Printf("Hola, Yo soy %s podes llamarme al %s\n", h.name, h.phone) + } + + func (e *Employee) SayHi() { + fmt.Printf("Hola, Yo soy %s, trabajo en %s. Podes llamarme al %s\n", e.name, + e.company, e.phone) //Si, lo podes cortar en dos líneas. + } + + func main() { + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} + sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + + mark.SayHi() + sam.SayHi() + } +``` +Ahora eres capaz de escribir programas que utilicen el paradigma Orientado a Objetos, los métodos utilizan la regla de que los que tienen nombre que se inician con mayúsculas van a ser públicos o privados en caso contrario. + +## Enlaces + +- [Índice](preface.md) +- Sección anterior: [struct](02.4.md) +- Siguiente sección: [interfaces](02.6.md) diff --git a/es/02.6.md b/es/02.6.md new file mode 100644 index 00000000..69181197 --- /dev/null +++ b/es/02.6.md @@ -0,0 +1,395 @@ +# 2.6 Interfaces + +## Interfaces + +Una de las características de diseño mas sutiles en Go son las interfaces. Después de leer esta sección, probablemente quedara impresionado por su implementación. + +### Que es una interfaz? + +En pocas palabras, una interfaz es un conjunto de métodos, que se usan para definir un conjunto de acciones. + +Al igual que en los ejemplos de las secciones anteriores, ambos, Student y Employee pueden llamar a `SayHi()`, pero ellos no hacen lo mismo. + +Vamos a trabajar un poco mas, vamos a agregarle a ellos el método `Sing()`, y también vamos a agregar `BorrowMoney()` a Student y `SpendSalary()` a Employee. + +Ahora Student tiene tres métodos llamados `SayHi()`, `Sing()`, `BorrowMoney()`, y Employee tiene `SayHi()`, `Sing()` y `SpendSalary()`. + +Esta combinación de métodos es llamada interfaz, y es implementada por Student y Employee. Entonces Student y Employee implementan la interfaz: `SayHi()`, `Sing()`. Al mismo tiempo, Employee no implementa la interfaz: `SayHi()`, `Sing()`, `BorrowMoney()`, y Student no implementa la interfaz: `SayHi()`, `Sing()`, `SpendSalary()`. Esto es porque Employee no tiene el método `BorrowMoney()` y Student no tiene el método `SpendSalary()`. + +### Tipo de Interfaces + +Una interfaz define un grupo de métodos, entonces si un tipo implementa todos los métodos entonces nosotros decimos que ese tipo implementa la interfaz. +``` + type Human struct { + name string + age int + phone string + } + + type Student struct { + Human + school string + loan float32 + } + + type Employee struct { + Human + company string + money float32 + } + + func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) + } + + func (h *Human) Sing(lyrics string) { + fmt.Println("La la, la la la, la la la la la...", lyrics) + } + + func (h *Human) Guzzle(beerStein string) { + fmt.Println("Guzzle Guzzle Guzzle...", beerStein) + } + + // Employee sobrescribe Sayhi + func (e *Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //Si acá podes cortarlo en 2 líneas. + } + + func (s *Student) BorrowMoney(amount float32) { + s.loan += amount // (otra vez...) + } + + func (e *Employee) SpendSalary(amount float32) { + e.money -= amount + } + + // definimos algunas interfaces + type Men interface { + SayHi() + Sing(lyrics string) + Guzzle(beerStein string) + } + + type YoungChap interface { + SayHi() + Sing(song string) + BorrowMoney(amount float32) + } + + type ElderlyGent interface { + SayHi() + Sing(song string) + SpendSalary(amount float32) + } +``` +Sabemos que una interfaz puede ser implementada por cualquier tipo, y un tipo puede implementar muchas interfaces al mismo tiempo. + +Tenga en cuenta que todos los tipos implementan la interfaz vacía `interface{}` porque esta no tiene ningún método y todos los tipos tienen cero métodos por defecto. + +### Valor de una interfaz + +Entonces, que tipo de valores se puede poner en una interfaz? Si definimos una variable como tipo interfaz, cualquier tipo que implemente la interfaz puede asignarse a esta variable. + +Al igual que en el ejemplo anterior, si definimos una variable m como la interfaz Men, entonces cualquier Student, Human o Employee puede ser asignado a m. Entonces podríamos tener una lista de Men, y cualquier tipo que implemente la interfaz Men puede agregarse a esa lista (slice). Sin embargo sea consciente que una lista de interfaces no tiene el mismo comportamiento que una lista de otros tipos. +``` + package main + + import "fmt" + + type Human struct { + name string + age int + phone string + } + + type Student struct { + Human + school string + loan float32 + } + + type Employee struct { + Human + company string + money float32 + } + + func (h Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) + } + + func (h Human) Sing(lyrics string) { + fmt.Println("La la la la...", lyrics) + } + + func (e Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //Si acá podes cortarlo en 2 líneas. + } + + // La interfaz Men es implementada por Human, Student y Employee + type Men interface { + SayHi() + Sing(lyrics string) + } + + func main() { + mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} + paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} + sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} + Tom := Employee{Human{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000} + + // definimos la interfaz i + var i Men + + //i podemos guardar un Student + i = mike + fmt.Println("This is Mike, a Student:") + i.SayHi() + i.Sing("November rain") + + //i podemos guardar un Employee + i = Tom + fmt.Println("This is Tom, an Employee:") + i.SayHi() + i.Sing("Born to be wild") + + // lista de Men + fmt.Println("Let's use a slice of Men and see what happens") + x := make([]Men, 3) + // estos tres elementos son de diferentes tipos pero todos ellos implementan la interfaz Men + x[0], x[1], x[2] = paul, sam, mike + + for _, value := range x { + value.SayHi() + } + } +``` + +Una interfaz es un conjunto de métodos abstractos, y puede ser implementada por tipos que no son interfaces. Por lo tanto esta no puede implementarse a si misma. + +### Interfaz vacía + +Una interfaz vacía es una interfaz que no contiene ningún método, entonces todos los tipos implementan una interfaz vacía. Es muy útil cuando buscamos guardar todos los tipos en el mismo lugar, y es similar a void* en C. +``` + // definimos una interfaz vacía + var a interface{} + var i int = 5 + s := "Hello world" + // a puede guardar un valor de cualquier tipo + a = i + a = s +``` +Si una función usa una interfaz vacía como su tipo de argumento, esta puede aceptar cualquier tipo; si una función usa una interfaz vacía como el tipo de valor de retorno, esta puede devolver cualquier tipo. + +### Argumentos de métodos de una interfaz + +Cualquier variable puede usarse en una interfaz, entonces podemos pensar sobre como podemos usar esta característica para pasar cualquier tipo de variable a una función. + +Por ejemplo, usamos mucho fmt.Println, pero alguna vez notaste que ¿acepta cualquier tipo de argumento? Si vemos el código fuente de fmt que es libre, podemos ver la siguiente definición. +``` + type Stringer interface { + String() string + } + ``` +Esto significa que cualquier tipo que implemente la interfaz Stringer puede ser pasada a fmt.Println como argumento. Vamos a probarlo. +``` + package main + + import ( + "fmt" + "strconv" + ) + + type Human struct { + name string + age int + phone string + } + + // Human implementa fmt.Stringer + func (h Human) String() string { + return "Name:" + h.name + ", Age:" + strconv.Itoa(h.age) + " years, Contact:" + h.phone + } + + func main() { + Bob := Human{"Bob", 39, "000-7777-XXX"} + fmt.Println("This Human is : ", Bob) + } +``` + +Volviendo atrás al ejemplo de Box, podemos ver que Color también implementa la interfaz Stringer, por lo que somos capaces de personalizar el formato de impresión. Si no implementamos esa interfaz, fmt.Println imprimirá el tipo con el formato por defecto. +``` + fmt.Println("The biggest one is", boxes.BiggestsColor().String()) + fmt.Println("The biggest one is", boxes.BiggestsColor()) +``` +Atención: Si el tipo implementa la interfaz `error`, fmt va a llamar a `error()`, entonces en este punto no tendrás que implementar Stringer. + +### Tipos de variables en un interfaz + +Si una variable es del tipo que implementa una interfaz, sabemos que cualquier otro tipo que implemente la misma interfaz puede ser asignada a esta variable. La pregunta es ¿Cómo podemos saber cual es el tipo específico almacenado en la interfaz? Tenemos dos formas, que te voy a comentar a continuación. + +- Patron de Aserción Comma-ok + +Go tiene la sintaxis `value, ok := element.(T)`. Esto comprueba si la variable es del tipo que se espero, donde value es el valor de la variable, y ok es un valor de tipo booleano, element es la variable interfaz y T es el tipo que se afirma tener. + +Si el elemento es del tipo que esperamos, ok será true, en otro caso será false. + +Veamos un ejemplo para verlo con más claridad. +``` + package main + + import ( + "fmt" + "strconv" + ) + + type Element interface{} + type List []Element + + type Person struct { + name string + age int + } + + func (p Person) String() string { + return "(Nombre: " + p.name + " - edad: " + strconv.Itoa(p.age) + " años)" + } + + func main() { + list := make(List, 3) + list[0] = 1 // un int + list[1] = "Hola" // una string + list[2] = Person{"Dennis", 70} + + for index, element := range list { + if value, ok := element.(int); ok { + fmt.Printf("list[%d] is an int and its value is %d\n", index, value) + } else if value, ok := element.(string); ok { + fmt.Printf("list[%d] is a string and its value is %s\n", index, value) + } else if value, ok := element.(Person); ok { + fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) + } else { + fmt.Println("list[%d] is of a different type", index) + } + } + } +``` +Es bastante sencillo usar este patrón, pero si tenemos que verificar varios tipos, es mejor que usemos un `switch`. + +- Verificación con un switch + +Vamos a ver el uso de `switch` para reescribir el ejemplo anterior. +``` + package main + + import ( + "fmt" + "strconv" + ) + + type Element interface{} + type List []Element + + type Person struct { + name string + age int + } + + func (p Person) String() string { + return "(Nombre: " + p.name + " - edad: " + strconv.Itoa(p.age) + " años)" + } + + func main() { + list := make(List, 3) + list[0] = 1 //un int + list[1] = "Hello" //una string + list[2] = Person{"Dennis", 70} + + for index, element := range list { + switch value := element.(type) { + case int: + fmt.Printf("list[%d] is an int and its value is %d\n", index, value) + case string: + fmt.Printf("list[%d] is a string and its value is %s\n", index, value) + case Person: + fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) + default: + fmt.Println("list[%d] is of a different type", index) + } + } + } +``` + +Una cosa que deberíamos recordar es que `element.(type)` no puede ser usado fuera del cuerpo del `switch`, lo que significa que en este caso no puedes usar el patrón `comma-ok`. + +### Interfaces embebidas + +La cosa más atractiva es que Go tiene mucha lógica embebida en su sintaxis, como campos anónimos en un struct. No es para sorprenderse, que podamos usar interfaces también como campos anónimos, pero vamos a llamarlas `Interfaces embebidas`. Aquí, vamos a seguir las mismas reglas que para los campos anónimos. Más específicamente, si una interfaz tiene otra interfaz como una interfaz embebida, esta tendrá todos los métodos que la clase embebida tiene. + +Podemos ver el archivo fuente `container/heap` que tiene una definición como la siguiente. +``` + type Interface interface { + sort.Interface // embebida sort.Interface + Push(x interface{}) //el método Push para empujar elementos a la pila + Pop() interface{} //el elemento Pop que saca elementos de la pila + } +``` +Podemos ver que `sort.Interface` es una interfaz embebida, por lo que la interfaz anterior tiene tres métodos que son explícitos de `sort.Interface`. +``` + type Interface interface { + // Len es el número de elementos en la colección. + Len() int + // Less devuelve si el elemento de índice i se debe ordenar + // antes que el elemento con índice j. + Less(i, j int) bool + // Swap intercambia los elementos con índices i y j. + Swap(i, j int) + } +``` +Otro ejemplo es el de `io.ReadWriter` en el paquete `io`. +``` + // io.ReadWriter + type ReadWriter interface { + Reader + Writer + } +``` +### Reflexión + +La reflexión en Go es usada para obtener información en tiempo de ejecución. Vamos a usar el paquete `reflect`, y este articulo oficial [articulo](http://golang.org/doc/articles/laws_of_reflection.html) explica como funciona la reflexión en Go. + +Tenemos que realizar tres pasos para usar reflect. Primero, necesitamos convertir una interfaz en un tipo reflect types (reflect.Type o reflect.Value, depende en que situación nos encontremos). +``` + t := reflect.TypeOf(i) // tomamos la meta-data de el tipo i, y usamos t para tomar todos los elementos + v := reflect.ValueOf(i) // tomamos el valor actual de el tipo i, y usamos v para cambiar este valor +``` +Después de eso, necesitamos convertir el tipo reflect de el valor que tomamos a el tipo que necesitamos. +``` + var x float64 = 3.4 + v := reflect.ValueOf(x) + fmt.Println("type:", v.Type()) + fmt.Println("kind is float64:", v.Kind() == reflect.Float64) + fmt.Println("value:", v.Float()) +``` +Finalmente, si buscamos cambiar el valor que vino del tipo reflects, necesitamos hacerlo modificable. Como hablamos antes, esta es una diferencia entre pasar por valor o por referencia. El siguiente código no compilará. +``` + var x float64 = 3.4 + v := reflect.ValueOf(x) + v.SetFloat(7.1) +``` +En lugar de eso, debemos usar el siguiente código para cambiar el valor de los tipos reflect. +``` + var x float64 = 3.4 + p := reflect.ValueOf(&x) + v := p.Elem() + v.SetFloat(7.1) +``` +Acabamos de hablar sobre los conocimientos básicos de sobre el uso de reflexión (reflection), deberías practicar más para entenderlo mejor. + +## Enlaces + +- [Índice](preface.md) +- Sección anterior: [Orientado a objetos](02.5.md) +- Siguiente sección: [Concurrencia](02.7.md) diff --git a/es/02.7.md b/es/02.7.md new file mode 100644 index 00000000..e6d0d053 --- /dev/null +++ b/es/02.7.md @@ -0,0 +1,242 @@ +# Concurrencia + +Se dice que Go es el lenguaje C del siglo 21. Pienso que hay dos razones para eso: la primera, Go es un lenguaje simple; segundo, la concurrencia es un tema candente en el mundo de hoy, y Go soporta esta característica a nivel de lenguaje. + +## goroutine + +goroutines y concurrencia están integradas en el diseño del núcleo de Go. Ellas son similares a los hilos pero trabajan de forma diferente. Más de una docena de goroutines a lo mejor por debajo solo tienen 5 o 6 hilos. Go también nos da soporte completo para compartir memoria entre sus goroutines. Una goroutine usualmente usa 4~5 KB de memoria en la pila. Por lo tanto, no es difícil ejecutar miles de goroutines en una sola computadora. Una goroutine es mas liviana, más eficiente, y más conveniente que los hilos del sistema. + +Las goroutines corren en el administrador de procesos en tiempo de ejecución en Go. Usamos la palabra reservada `go` para crear una nueva goroutine, que por debajo es una función ( ***main() es una goroutine*** ). +``` + go hello(a, b, c) +``` +Vamos a ver un ejemplo. +``` + package main + + import ( + "fmt" + "runtime" + ) + + func say(s string) { + for i := 0; i < 5; i++ { + runtime.Gosched() + fmt.Println(s) + } + } + + func main() { + go say("world") // creamos una nueva goroutine + say("hello") // actual goroutine + } +``` +Salida: +``` + hello + world + hello + world + hello + world + hello + world + hello +``` +Podemos ver que es muy fácil usar concurrencia en Go usando la palabra reservada `go`. En el ejemplo anterior, estas dos goroutines comparten algo de memoria, pero sería mejor si utilizáramos la receta de diseño: No utilice datos compartidos para comunicarse, use comunicación para compartir datos. + +runtime.Gosched() le dice al CPU que ejecute otras goroutines, y que en algún punto vuelva. + +El manejador de tareas solo usa un hilo para correr todas la goroutines, lo que significa que solo implementa la concurrencia. Si buscas utilizar mas núcleos del CPU para usar mas procesos en paralelo, tenemos que llamar a runtime.GOMAXPROCS(n) para configurar el numero de núcleos que deseamos usar. Si `n<1`, esto no va a cambiar nada. Esta función se puede quitar en el futuro, para ver mas detalles sobre el procesamiento en paralelo y la concurrencia vea el siguiente [articulo](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide). + +## Canales + +goroutines son ejecutadas en el mismo espacio de direcciones de memoria, por lo que se tiene que mantener sincronizadas si buscas acceder a la memoria compartida. ¿Cómo nos comunicamos entre diferentes goroutines? Go utiliza un muy buen mecanismo de comunicación llamado `canales`(channel). Los `canales` son como dos tuberías (o pipes) en la shell de Unix: usando canales para enviar o recibir los datos. El unico tipo de datos que se puede usar en los canales es el tipo `channel` y la palabra reservada para eso es `chan`. Ten en cuenta que para crear un nuevo `channel` debemos usar la palabra reservada `make`. +``` + ci := make(chan int) + cs := make(chan string) + cf := make(chan interface{}) +``` +Los canales usan el operador `<-` para enviar o recibir datos. +``` + ch <- v // enviamos v al canal ch. + v := <-ch // recibimos datos de ch, y lo asignamos a v +``` +Vemos esto en un ejemplo. +``` + package main + + import "fmt" + + func sum(a []int, c chan int) { + total := 0 + for _, v := range a { + total += v + } + c <- total // enviamos total a c + } + + func main() { + a := []int{7, 2, 8, -9, 4, 0} + + c := make(chan int) + go sum(a[:len(a)/2], c) + go sum(a[len(a)/2:], c) + x, y := <-c, <-c // recibimos de c + + fmt.Println(x, y, x + y) + } +``` +Enviando y recibimos los datos por defecto en bloques, por lo que es mucho mas fácil usar goroutines sincrónicas. Lo que quiero decir, es que el bloque en la goroutine no va a continuar cuando reciba datos de un canal vacío (`value := <-ch`), hasta que otras goroutines envíen datos a este canal. Por otro lado, la goroutine por otro lado no enviara datos al canal (`ch<-5`) hasta que no reciba datos. + +## Buffered channels + +Anteriormente hice una introducción sobre canales non-buffered channels (non-buffered channels), y Go también tiene 'buffered channels' que pueden guardar mas de un elemento. Por ejemplo, `ch := make(chan bool, 4)`, aquí creamos un canal que puede guardar 4 elementos booleanos. Por lo tanto con este canal, somos capaces de enviar 4 elementos sin el bloqueo, pero la goroutine se bloqueará cuando intente enviar un quinto elemento y la goroutine no lo recibirá. +``` + ch := make(chan type, n) + + n == 0 ! non-buffer(block) + n > 0 ! buffer(non-block until n elements in the channel) +``` +Puedes probar con este código en tu computadora y cambiar algunos valores. +``` + package main + + import "fmt" + + func main() { + c := make(chan int, 2) // si cambia 2 por 1 tendrá un error en tiempo de ejecución, pero 3 estará bien + c <- 1 + c <- 2 + fmt.Println(<-c) + fmt.Println(<-c) + } +``` +## Range y Close + +Podemos usar range para para hacer funcionar los 'buffer channels' como una lista y un map. +``` + package main + + import ( + "fmt" + ) + + func fibonacci(n int, c chan int) { + x, y := 1, 1 + for i := 0; i < n; i++ { + c <- x + x, y = y, x + y + } + close(c) + } + + func main() { + c := make(chan int, 10) + go fibonacci(cap(c), c) + for i := range c { + fmt.Println(i) + } + } +``` +`for i := range c` no parara de leer información de el canal hasta que el canal se alla cerrado. Vamos a usar la palabra reservada `close` para cerrar el canal en el ejemplo anterior. Es imposible enviar o recibir datos de un canal cerrado, puede usar `v, ok := <-ch` para verificar si el canal esta cerrado. Si `ok` devuelve false, esto significa que no hay datos en ese canal y este fue cerrado. + +Recuerde cerrar siempre los canales productores, no los consumidores, o sera muy fácil obtener un estado de pánico o 'panic status'. + +Otra cosa que deber tener que recordar es que los canales son diferentes a los archivos, y no debe cerrarlos con frecuencia, a menos que este seguro que es canal esta completamente sin uso, o desea salir del bloque donde usa 'range'. + +## Select + +En los ejemplos anteriores, nosotros usamos solo un canal, pero ¿cómo podemos lidiar con más de un canal? Go tiene la palabra reservada llamada `select` para escuchar muchos canales. + +`select` de forma predeterminada es bloqueante, y este continua la ejecución solo cuando un canal tiene datos o recibió datos. Si varios canales están listos para usarse al mismo tiempo, select elegirá cual ejecutar al azar. +``` + package main + + import "fmt" + + func fibonacci(c, quit chan int) { + x, y := 1, 1 + for { + select { + case c <- x: + x, y = y, x + y + case <-quit: + fmt.Println("quit") + return + } + } + } + + func main() { + c := make(chan int) + quit := make(chan int) + go func() { + for i := 0; i < 10; i++ { + fmt.Println(<-c) + } + quit <- 0 + }() + fibonacci(c, quit) + } +``` +`select` también tiene `default`, al igual que el `switch`. Cuando todos los canales no están listos para ser usados, ejecuta el default (no espera mas por el canal). +``` + select { + case i := <-c: + // usa i + default: + // se ejecuta cuando c esta bloqueado + } +``` +## Timeout + +A veces la goroutine esta bloqueada, ¿pero como podemos evitar que esto, mientras tanto nos bloquee el programa? Podemos configurar para esto un timeout en el select. +``` + func main() { + c := make(chan int) + o := make(chan bool) + go func() { + for { + select { + case v := <- c: + println(v) + case <- time.After(5 * time.Second): + println("timeout") + o <- true + break + } + } + }() + <- o + } +``` +## Goroutine en tiempo de ejecución (o runtime) + +El paquete `runtime` tiene algunas funciones para hacer frente a las goroutines. + +- `runtime.Goexit()` + + Sale de la actual goroutine, pero las funciones defer son ejecutadas como de costumbre. + +- `runtime.Gosched()` + + Permite que el manejador de tareas ejecute otras goroutines, y en algún momento vuelve allí. + +- `runtime.NumCPU() int` + + Devuelve el numero de núcleos del CPU + +- `runtime.NumGoroutine() int` + + Devuelve el numero de goroutines + +- `runtime.GOMAXPROCS(n int) int` + + Configura cuantos núcleos del CPU queremos usar + +## Enlaces + +- [Índice](preface.md) +- Sección anterior: [interfaces](02.6.md) +- Siguiente sección: [Resumen](02.8.md) diff --git a/es/02.8.md b/es/02.8.md new file mode 100644 index 00000000..be3a1067 --- /dev/null +++ b/es/02.8.md @@ -0,0 +1,32 @@ +# 2.8 Resumen + +En este capitulo principalmente introducimos 25 palabras reservadas de Go. Vamos a repasar cuales son y que hacen. + + break default func interface select + case defer go map struct + chan else goto package switch + const fallthrough if range type + continue for import return var + +- `var` y `const` son usadas para definir variables y constantes. +- `package` e `import` son para el uso de paquetes. +- `func` es usada para definir funciones y métodos. +- `return` es usada para devolver valores en funciones o métodos. +- `defer` es usada para definir funciones defer. +- `go` es usada para comenzar a correr una nueva goroutine. +- `select` es usada para elegir entre múltiples canales de comunicación. +- `interface` es usada para definir una interfaz. +- `struct` es usada para definir tipos especiales personalizados. +- `break`, `case`, `continue`, `for`, `fallthrough`, `else`, `if`, `switch`, `goto`, `default` fueron introducidos en la sección 2.3. +- `chan` es usada para crear tipos de canales para comunicarse con las goroutines. +- `type` es usada para definir tipos personalizados. +- `map` es usada para definir un map que es como una tabla hash en otros lenguajes. +- `range` es usada para leer datos desde un `slice`, `map` o `channel`. + +Si entendes como usar estas 25 palabras reservadas, usted ya a aprendido mucho sobre Go. + +## Enlaces + +- [Índice](preface.md) +- Sección anterior: [Concurrencia](02.7.md) +- Siguiente sección: [Conocimientos básicos sobre la Web](03.0.md) diff --git a/es/03.0.md b/es/03.0.md new file mode 100644 index 00000000..a744958f --- /dev/null +++ b/es/03.0.md @@ -0,0 +1,9 @@ +#3 Conocimientos básicos sobre la Web + +La razón por la que estas leyendo este libro, es porque estas buscando aprender a desarrollar aplicaciones web con Go. Como dije antes, Go nos provee muchos paquetes con un gran potencial como es el `http`. Este nos ayudará mucho cuando queramos desarrollar aplicaciones web. En los siguientes capítulos te voy a enseñar todo lo que necesita saber, en este capítulo vamos a hablar sobre algunos conceptos sobre la web y como correr aplicaciones web en Go. + +## Enlaces + +- [Indice](preface.md) +- Capítulo anterior: [Capitulo 2 Resumen](02.8.md) +- Siguiente sección: [Principio para el trabajo en la Web](03.1.md) diff --git a/es/03.1.md b/es/03.1.md new file mode 100644 index 00000000..7f5b9ddb --- /dev/null +++ b/es/03.1.md @@ -0,0 +1,155 @@ +# 3.1. Principios para el trabajo en la Web + +Cada vez que abres tu navegador, escribes alguna direccion URL y pulsas ENTER, verás hermosas páginas web que aparecen en pantalla. Pero ¿sabes lo que está sucediendo detrás de esta simple acción? + +Normalmente, el navegador es un cliente, después de teclear la URL, envía peticiones a un servidor DNS, para obtener la dirección IP de la URL. Luego de encontrar el servidor en esta dirección IP, configura una conexión TCP. El navegador envía las peticiones HTTP a través de la conección. El servidor las maneja y responde con respuestas HTTP que conteienne el contenido que puedes ver en tu página web. Finalmente, el navegador renderiza el cuerpo de la página web y se desconecta del servidor. + +![](images/3.1.web2.png?raw=true) + +Figura 3.1 Procesos de usuarios que visitan una página web + +Un servidor web también conocido como un servidor HTTP, utiliza el protocolo HTTP para comunicarse con los clientes. Todos los navegadores web pueden ser vistos como clientes. + +Podemos dividir los principios de trabajo de Internet en los pasos siguientes: + +- El cliente utiliza el protocolo TCP / IP para conectarse al servidor. +- El cliente envía paquetes de solicitud HTTP al servidor. +- El servidor devuelve los paquetes de respuesta HTTP para el cliente, si los recursos de petición incluyen scripts dinámicos , el servidor llama al motor de scripts primero. +- El cliente se desconecta del servidor, comienza renderizado HTML. + +Este es un sencillo flujo de trabajo de asuntos HTTP, observe que el servidor despues de un tiempo cierra las conexiones de datos que se envían a los clientes, y espera a que la próxima petición. + +## URL y la resolución de DNS + +Siempre estamos utilizando URL para acceder a páginas web , pero ¿sabes cómo funciona el URL ? + +El nombre completo de la dirección URL es Uniform Resource Locator (Localizador de Recursos Uniforme), esto es para la descripción de recursos en Internet. Su forma básica como sigue . +``` + esquema://host[:port #]/ruta/.../[? cadena de consulta ][# ancla ] + esquema: asignación de protocolo subyacente (como HTTP , HTTPS, FTP ) + host: IP o nombre de dominio del servidor HTTP + puerto#: puerto por defecto es 80 , y se puede omitir en este caso. + Si desea utilizar otros puertos , debe especificar qué puerto . Por ejemplo , + http://www.cnblogs.com:8080/ + ruta: ruta de los recursos + datos: datos de consulta se envían al servidor + ancla Ancla +``` +DNS es la abreviatura de Domain Name Server (Servidor de Nombres de Dominio), que es el sistema de nombres de servicios informáticos en red, convierte nombres de dominio a direcciones IP reales, al igual que un traductor. + +![](images/3.1.dns_hierachy.png?raw=true) + +Figura 3.2 Principios de funcionamiento de DNS + +Para entender más acerca de su principio de funcionamiento, veamos en detalle el proceso de resolución de DNS de la siguiente manera. + +1. Después de escrito el nombre de dominio www.qq.com en el navegador , el sistema operativo comprueba si hay alguna relación de correspondencia en el archivo hosts para el nombre de dominio, si es así, termina la resolución de nombres de dominio. +2. Si no hay relación de proyección en el archivo hosts, el sistema operativo comprueba si hay alguna caché en el DNS, si es así, termina la resolución de nombres de dominio. +3. Si no hay relación entre el computador y el servirdor de caché DNS, el sistema operativo busca el primer servidor de resolución de DNS en la configuración de TCP / IP, que es el servidor DNS local en este momento. Cuando el servidor DNS local recibió consulta, si el nombre de dominio que desea consultar está contenida en la configuración local de los recursos regionales, a continuación, devuelve los resultados al cliente . Esta resolución DNS es autoritaria . +4. Si el servidor DNS local no contiene el nombre de dominio , y hay una relación de correspondencia en con el servidor caché DNS, el servidor DNS local devuelve este resultado a cliente. Esta resolución DNS no es autoritaria. +5. Si el servidor DNS local no puede resolver el nombre de dominio , ya sea por la configuración de los recursos regionales o caché, se procede al próximo paso, que depende de la configuración del servidor DNS local. Si el servidor local de DNS no permite seguir avanzando, el enruta la petición a la raiz del servidor DNS, entonces returna la dirección IP de un servidor DNS de alto nivel, que debería conocer el nombre de dominio, `.com` en este caso. Si el primer servidor de mas alto nivel no reconoce el nombre de dominio, de nuevo re enruta la petición al siguiente servidor DNS de mayor nivel, así sucesivamente hasta que encuentre un servidor DNS que pueda reconocer el nombre de dominio. Entonces, se le solicita al servidor DNS la IP correspondiente al dominio, en este caso `www.qq.com` +- Si el servidor DNS local habilita seguir adelante, envía la solicitud al servidor DNS de nivel superior , si el servidor DNS de nivel superior también no sabe el nombre de dominio , a continuación, seguira enviando solicitudes a nivel superior. +- Si el servidor DNS local permite a modo de avance , la dirección IP del servidor de nombre de dominio devuelve al servidor DNS local , y el servidor local envía a los clientes. + +Sea que el cliente DNS local habilite o no el redireccionamiento, la dirección IP del dominio siempre se retornará al serviror local de DNS, y el servidor local DNS se lo enviará de vuelta al cliente + +![](images/3.1.dns_inquery.png?raw=true) + +Figura 3.3 DNS flujo de trabajo de resolución. + +`Proceso de consulta recursiva` significa que las solicitudes de información están cambiando en el proceso, encambio las solicitudes de información no cambian en el proceso de consulta iterativa . + +Ahora sabemos que los clientes obtendran direcciones IP , al final, por lo que los navegadores estan comunicandose con los servidores a través de las direcciones IP. + +## Protocolo HTTP + +Protocolo HTTP es la parte fundamental de los servicios web. Es importante saber lo que es el protocolo HTTP antes de entender cómo funciona la web. + +HTTP es el protocolo que se utiliza para la comunicación entre navegadores y servidores web, se basa en el protocolo TCP, y por lo general utilizan el puerto 80 en el servidor web. Es un protocolo que utiliza el modelo de petición-respuesta, los clientes envían peticiones y el servidor responde. De acuerdo con el protocolo HTTP, los clientes siempre configuran una nueva conexión y envian una petición HTTP al servidor en cada asunto. Los servidor no son capaces de conectar con el cliente de forma proactiva, o establecer conexiones que inicien desde ellos. La conexión entre el cliente y el servidor puede ser cerrada por cualquier lado. Por ejemplo, puedes cancelar su tarea de descarga y conexión HTTP tu navegador se desconecta del servidor antes de que termine la descarga. + +El protocolo HTTP no tiene estado, lo que significa que el servidor no tiene idea acerca de la relación entre dos conexiones, a pesar de que puedan ser ambas de un mismo cliente. Para solucionar este problema, las aplicaciones web usan Cookies para mantener el estado sostenible de conexiones. + +Por esta causa, debido a que el protocolo HTTP se basa en el protocolo TCP , todos los ataques TCP afectarán a la comunicación HTTP en el servidor, como por ejemplo SYN Flood, DoS y DDoS . + +### Paquete de solicitud HTTP (información del navegador). + +Solicitar paquetes tienen tres partes: la línea de petición , solicitud de encabezado y cuerpo . Hay una línea en blanco entre la cabecera y el cuerpo. +``` + GET/domains/ejemplo/ HTTP/1.1 // línea de solicitud : método de la petición , la dirección URL , el protocolo y su versión. + Host: www.iana.org / / nombre de dominio + User-Agent : Mozilla/5.0 (Windows NT 6.1 ) AppleWebKit/537.4 ( KHTML , like Gecko ) Chrome/22.0.1229.94 Safari/537.4 // información del navegador + Accept: text/html,application/xhtml+xml,application/xml;q=0,9,*/*;q=0.8 // MIME que los clientes pueden aceptar + Accept- Encoding : gzip, deflate, sdch // métodos de compresión + Accept- Charset : UTF- 8 , * ; q = 0.5 / / conjunto de caracteres en el lado del cliente + // Línea en blanco + // Cuerpo , los argumentos de recursos solicitud (por ejemplo , los argumentos de la POST ) +``` +Utilizamos fiddler que obtener la siguiente información de la solicitud. + +![](images/3.1.http.png?raw=true) + +Figura 3.4 Información de método GET capturado por fiddler + +![](images/3.1.httpPOST.png?raw=true) + +Figura 3.5 Información de método POST capturado por fiddler + +**Podemos ver que el método GET no tiene cuerpo de la solicitud, el método POST si.** + +Hay muchos métodos que puede utilizar para comunicarse con los servidores de HTTP ; GET, POST, PUT, DELETE son los 4 métodos básicos que utilizamos. Una URL representa un recurso en la red, por lo que estos 4 métodos significan consultar, cambiar, agregar y eliminar operaciones. GET y POST son los más utilizados en HTTP. GET puede añadir los parámetros de búsqueda usando el separador `?` para separa la URL de los parámetros y `&` entre parámetros como por ejemplo: `EditPosts.aspx?name=test1&id=123456`. POST puede colocar muchísima mas información en el cuerpo d ela petición, porque la URL solo puede tener un tamaño máximo, dependiendo del navegador. También, cuando pasamos nuestro nombre de usuario y contraseña , no queremos que este tipo de información aparesca en la URL, por lo que utilizamos la POST para mantenerlos invisibles. + +### Paquete de respuesta HTTP (información del servidor) + +Vamos a ver qué tipo de información se incluye en los paquetes de respuesta. +``` + HTTP/1.1 200 OK // estado + Server : nginx/1.0.8 // web server y su versión en el equipo servidor + Date:Date: Tue, 30 Oct 2012 04:14:25 GMT // hora en que se respondió + Content-Type : text / html // tipo de datos que responde + Transfer-Encoding: chunked // significa que los datos se envían en fragmentos + Conexión : keep-alive // mantener la conexión + Content-Length: 90 // longitud del cuerpo + // Línea en blanco + max { + tempDelay = max + } + log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay) + time.Sleep(tempDelay) + continue + } + return e + } + tempDelay = 0 + c, err := srv.newConn(rw) + if err != nil { + continue + } + go c.serve() + } + } +``` + +¿Cómo aceptamos las peticiones de un cliente después que iniciamos a escuchar en un puerto? En el código fuente , podemos ver que se llama `srv.Serve(net.Listener)` para manejar peticiones de clientes. En el cuerpo de la función hay un `for{}`, se acepta la solicitud, se crea una nueva conexión y, a continuación, se inicia un nuevo goroutine , y pasa los datos de solicitud a esta goroutine: `go c.serve()`. Así es como Go es compatible con alta concurrencia, y cada goroutine es independiente. + +¿Cómo usamos las funciones específicas para controlar las solicitudes? `conn` analiza la solicitud `c.ReadRequest()` al principio, y obtiene el controlador correspondiente `handler : = c.server.Handler` que es el segundo argumento que pasábamos cuando llamamos `ListenAndServe`. Como pasamos `nil`, Go usa su manejador por defecto `handler = DefaultServeMux`. Entonces, ¿qué está haciendo `DefaultServeMux` aquí? Bueno, esta es la variable de enrutador en este momento, se llama a las funciones de controlador de URL específicas . ¿Hemos asignado esto? Sí, lo hicimos. Recuerde que en la primera línea se utilizó `http.HandleFunc("/", sayhelloName)`. Es asi como se utiliza esta función para registrar el estado del router para la ruta "/". Cuando la dirección URL es `/`, el enrutador llama a la función `sayhelloName` . DefaultServeMux llama ServerHTTP para obtener la función de controlador de ruta diferente, y llama `sayhelloName` en este caso. Por último , el servidor escribe los datos y la respuesta a los clientes. + +Flujo de trabajo detallado: + +![](images/3.3.illustrator.png?raw=true) + +Figura 3.10 Flujo de trabajo de manejar una petición HTTP + +Creo que ahora debes saber cómo Go se ejecuta servidores web ahora. + +## Enlaces + +- [Indice](preface.md) +- Sección anterior: [Armando un servidor web sencillo](03.2.md) +- Siguiente sección: [Obteniendo el paquete http](03.4.md) diff --git a/es/03.4.md b/es/03.4.md new file mode 100644 index 00000000..707fe52e --- /dev/null +++ b/es/03.4.md @@ -0,0 +1,203 @@ +# 3.4 Obteniendo el paquete http + +En las secciones anteriores, hemos aprendido sobre el flujo de trabajo de la web, y hablamos un poco sobre el paquete `http`. En esta sección, vamos a aprender dos funciones básicas que estan en el paquete `http`: Conn, ServeMux. + +## goroutine en Conn + +A diferencia de los servidores HTTP normales, Go utiliza goroutine para toda trabajo inicializado por Conn con el fin de lograr una alta concurrencia y rendimiento, por lo que cada trabajo es independiente. + +Go usa el siguiente código para esperar a nuevas conexiones de clientes . +``` + c, err := srv.newConn(rw) + if err != nil { + continue + } + go c.serve() +``` +Como puedes ver, se crea una goroutine para cada conexión , y se pasa el controlador que es capaz de leer los datos de solicitud a la goroutine. + +## ServeMux personalizado + +Utilizamos el enrutamiento por defecto en la sección anterior, cuando hablamos de conn.server, el router pasa los datos de solicitud como back-end al controlador. + +El struct del router por defecto: +``` + type ServeMux struct { + mu sync.RWMutex //debido a la concurrencia, tenemos que utilizar mutex aquí + m map[string]muxEntry //routers, cada asignación de cadena a un controlador + } +``` +El struct de muxEntry: +``` + type muxEntry struct { + explicit bool // exact match or not + h Handler + } +``` +La interfaz de Handler: +``` + type Handler interface { + ServeHTTP(ResponseWriter, *Request) // routing implementer + } +``` +`Handler` es una interfaz, pero la función `sayhelloName` no implementa su interfaz, entonces ¿cómo podríamos agregarla como controlador? Debido a que hay otro tipo `HandlerFunc` en el paquete `http`. Nosotros llamamos `HandlerFunc` para definir nuestro método `sayhelloName` , así `sayhelloName` implementa el `Handler` al mismo tiempo. Es como si llamaramos `HandlerFunc(f)`, y la función `f` es forzado a convertirce al tipo `HandlerFunc`. +``` + type HandlerFunc func(ResponseWriter, *Request) + + // ServeHTTP calls f(w, r). + func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { + f(w, r) + } +``` +¿Cómo enrutador llama los controladores después de establecer reglas del router? + +El enrutador llama `mux.handler.ServeHTTP(w , r)` cuando recibe solicitudes. En otras palabras, se llama la interfaz `ServeHTTP` de los controladores. + +Ahora, vamos a ver cómo funciona `mux.handler`. +``` + func (mux *ServeMux) handler(r *Request) Handler { + mux.mu.RLock() + defer mux.mu.RUnlock() + + // Host-specific pattern takes precedence over generic ones + h := mux.match(r.Host + r.URL.Path) + if h == nil { + h = mux.match(r.URL.Path) + } + if h == nil { + h = NotFoundHandler() + } + return h + } +``` +El enrutador utiliza la URL como llave para encontrar el controlador correspondiente que guarda en un mapa, luego llama handler.ServeHTTP para ejecutar funciones y manejar los datos. + +En este punto, debes entender el flujo de trabajo del enrutador, y Go realmente apoya routers personalizados. El segundo argumento de ListenAndServe es para la configuración del enrutadores a la medida, entonces cualquier enrutador que implemente la interfaz de `Handler` puede ser utilizado. + +El siguiente ejemplo muestra cómo implementar un enrutador sencillo. +``` + package main + + import ( + "fmt" + "net/http" + ) + + type MyMux struct { + } + + func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + sayhelloName(w, r) + return + } + http.NotFound(w, r) + return + } + + func sayhelloName(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello myroute!") + } + + func main() { + mux := &MyMux{} + http.ListenAndServe(":9090", mux) + } +``` +# Enrutamiento + +Si no quieres usar un enrutador, todavía puedes lograr lo que escribimos en la sección de arriba reemplazando el segundo argumento de `ListenAndServe` a nil, y registrando las URLS usando un `HandleFunc` función que recorre todas las URLs registradas para encontrar la mejor coincidencia, entonces debemos preocuparnos por el orden de registro. + +código de ejemplo: +``` + http.HandleFunc("/", views.ShowAllTasksFunc) + http.HandleFunc("/complete/", views.CompleteTaskFunc) + http.HandleFunc("/delete/", views.DeleteTaskFunc) + + //ShowAllTasksFunc es usado para manejar la URL "/" que tiene por defecto todo + //TODO agregar manejador para 404 + func ShowAllTasksFunc(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + context := db.GetTasks("pending") //true cuando tu no quieres eliminar tareas + //db es un paquete que interactua con la base de datos + if message != "" { + context.Message = message + } + homeTemplate.Execute(w, context) + message = "" + } else { + message = "Method not allowed" + http.Redirect(w, r, "/", http.StatusFound) + } + } +``` +Esto está bien para aplicaciones simples las cuales no necesitan ruteos parametrizados, pero ¿cuándo necesitas eso? Puedes usar las herramientas o frameworks, pero como este libro está enfocado en crear aplicaciones web en Go, vamos a enseñarte como manejar ese escenario también. + +Cuando la concidencia es hecha, se llama a la función definida en `HandleFunc`, así que supongamos que estamos escribiendo un manejador para una lista y queremos eliminar una tarea, así que la aplicación decide que función se va a llamar cuando llegue la petición `/delete/1`, entonces registramos la URL de la siguiente manera: + `http.HandleFunc("/delete/", views.DeleteTaskFunc)` + `/delete/1` this URL matches closest with the "/delete/" URL than any other URL so in the `r.URL.path` we get the entire URL of the request. +``` + http.HandleFunc("/delete/", views.DeleteTaskFunc) + //DeleteTaskFunc is used to delete a task, trash = move to recycle bin, delete = permanent delete + func DeleteTaskFunc(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + id := r.URL.Path[len("/delete/"):] + if id == "all" { + db.DeleteAll() + http.Redirect(w, r, "/", http.StatusFound) + } else { + id, err := strconv.Atoi(id) + if err != nil { + fmt.Println(err) + } else { + err = db.DeleteTask(id) + if err != nil { + message = "Error deleting task" + } else { + message = "Task deleted" + } + http.Redirect(w, r, "/", http.StatusFound) + } + } + } else { + message = "Method not allowed" + http.Redirect(w, r, "/", http.StatusFound) + } + } +``` + Enlace: https://github.com/thewhitetulip/Tasks/blob/master/views/views.go#L170-#L195 + + Este método lo que hace básicamente es que la función que maneja la URL `/delete/`, se toma la URL completa, que es `/delete/1`, se toman segmentos de la cadena y se extraen todo lo que después de la cadena de coincidencia, en este caso es `1`. Entonces usamos el paquete `strconv` para convertir la cadena en entero y eliminar la tarea con ese identificador. + +En escenarios mas complejos también podemos utilizar este método, la ventaja es que no vamos a tener que usar herramientas de terceros, pero las herramientas de terceros tienden a ser útiles en su sentido propio. Tienes que tomar la decisión de cuál método prefieres. Ninguna respuesta es la respuesta correcta. + + +## Flujo de ejecución del código en GO + +Vamos a echar un vistazo a la lista de flujo de ejecución en conjunto. + +- Se llama http.HandleFunc + 1. Se Llama HandleFunc de DefaultServeMux + 2. Se Llama Handle de DefaultServeMux + 3. Se agregan las reglas del enrutamiento a map[string]muxEntry de DefaultServeMux +- Se llama http.ListenAndServe (":9090" , nil ) + 1. Se instancia el servidor + 2. Llama ListenAndServe del Servidor + 3. Llama net.Listen ( " tcp" , addr ) para escuchar en el puerto . + 4. Iniciar un bucle, y aceptar las solicitudes en el cuerpo del bucle. + 5. Instanciada una Conn se empieza una goroutine para cada solicitud : ir c.serve (). + 6. Lee petición de datos : w , err : = c.readRequest (). + 7. Comprueba si el controlador está vacío, si está vacíoutiliza DefaultServeMux . + 8. Llama al controlador de ServeHTTP + 9. Ejecutar código en DefaultServeMux en este caso. + 10. Elije el controlador URL y ejecutar código del controlador en esta seccion: mux.handler.ServeHTTP ( w , r) + 11. Cómo elegir handler: + A. Normas de router de verificación para esta URL. + B. Llamar ServeHTTP en ese controlador, si es que existe. + C. Llamar ServeHTTP de NotFoundHandler lo contrario. + +## Enlaces + +- [Indice](preface.md) +- Sección anterior: [Como trabaja Go con la web](03.3.md) +- Siguiente sección: [Resumen](03.5.md) diff --git a/es/03.5.md b/es/03.5.md new file mode 100644 index 00000000..d4372107 --- /dev/null +++ b/es/03.5.md @@ -0,0 +1,11 @@ +# 3.5 Resumen + +En este capítulo, presentamos HTTP, el flujo de la resolución DNS y cómo construir un servidor Web simple. Entonces hablamos de cómo Go implementa servidor web para nosotros al ver el código fuente del paquete net/http. + +Espero que ahora conozcas mucho más sobre el desarrollo web, y deberías ver que es muy fácil y flexible crear una aplicación web en Go. + +## Enlaces + +- [Indice](preface.md) +- Sección anterior: [Obteniendo el paquete http](03.4.md) +- Siguiente sección: [Formulario de entrada de los usuario](04.0.md) diff --git a/es/04.0.md b/es/04.0.md new file mode 100644 index 00000000..0fae2308 --- /dev/null +++ b/es/04.0.md @@ -0,0 +1,23 @@ +# 4 Formularios de Usuarios + +Un formulario de usuario es algo que es usado comunmente cuando desarrollamos aplicaciones web. El provee la habilidad de comunicarse entre clientes y servidores. Debes estar familiarizado con los formularios si eres un desarrollador web; si eres un programador C/C++ te puedes estar preguntando, ¿qué es un formulario de usuario? + +Un formulario es un área que contiene elementos de de formulario. Los usuarios pueden ingresar información en los elementos como en campos de texto, listas desplegables, botones circulares, cajas de chequeo, etc. nOsotros utilizamos la etiqueta `
` para definir formularios. +``` + + ... + elementos de entrada + ... +
+``` +Go tiene muchas funciones muy convenientes para lidiar con formularios de usuario. Facilmente puedes obtener la información de un formulario en una petición HTTP, y se pueden integrar fácilmente en tus aplicaciones propias. En la sección 4.1, vamos a hablar de como manejar la información de los formularios en Go. También que no puedes confiar en cualquier datao que viene del lado del cliente, primero debes validar los datos antes de usarlos. Vamos a ir a través de algunos ejemplos de como podemos validar la información de los formularios en la sección 4.2. + +Decimos que HTTP es un procolo sin estado. ¿Cómo podemos identificar que varios formularios vienen del mismo usuario? y ¿cómo nos damos cuenta que un formulario solo puede ser enviado una vez? Miraremos algunos detalles concernientes a las cookies (una cookie es un segmento de información que puede ser guardado en el lado del cluente y agregado al encabezado de la petición cuando la petición es enviada al servidor) en los capítulos 4.3 y 4.4. + +Otro caso de uso común para los formularios es subir archivos. En la sección 4.5, aprenderás como hacer esto y también como controlar el tamaño del archivo que vas a subir antes de subirlo, en Go. + +## Links + +- [Índice](preface.md) +- Sección anterior: [Resumen](03.5.md) +- Siguiente sección: [Procesando la entrada de los formularios](04.1.md) diff --git a/es/04.1.md b/es/04.1.md new file mode 100644 index 00000000..3ec9a73f --- /dev/null +++ b/es/04.1.md @@ -0,0 +1,107 @@ +# 4.1 Procesando las entradas de los formularios + +Antes de comenzar, vamos a echarle un vistazo a un formulario típico de usuario, guardado como `login.gtpl` en tu carpeta de proyecto. +``` + + + + + +
+ Nombre de Usuario: + Contraseña: + +
+ + +``` +Este formulario va a ser enviado a `/login` en el servidor. Después que el usuario de click en el botón Ingresar, la información va a ser enviada al manejador `login` registrado en nuestro enrutador. Entonces necesitamos saber qué método se usa, el POST o el GET. + +Es fácil darnos cuenta usando el paquete `http`. Veamos como manejar los formularios en la página de Ingreso. +``` + package main + + import ( + "fmt" + "html/template" + "log" + "net/http" + "strings" + ) + + func sayhelloName(w http.ResponseWriter, r *http.Request) { + r.ParseForm() // Análizar los parámetros URL enviados, entonces analizar el paquete para analizar el cuerpo del paquete, para peticiones POST. + // precaución: Si no se llama al método ParseForm, la siguiente unformación no podra ser obtenida del Formulario + fmt.Println(r.Form) // Imprime la información del lado del servidor + fmt.Println("path", r.URL.Path) + fmt.Println("scheme", r.URL.Scheme) + fmt.Println(r.Form["url_long"]) + for k, v := range r.Form { + fmt.Println("key:", k) + fmt.Println("val:", strings.Join(v, "")) + } + fmt.Fprintf(w, "Hello astaxie!") // Escrite la respuesta + } + + func login(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) //get request method + if r.Method == "GET" { + t, _ := template.ParseFiles("login.gtpl") + t.Execute(w, nil) + } else { + r.ParseForm() + // Parte lógica de la función login + fmt.Println("username:", r.Form["username"]) + fmt.Println("password:", r.Form["password"]) + } + } + + func main() { + http.HandleFunc("/", sayhelloName) // Definimos la regla del enrutador + http.HandleFunc("/login", login) + err := http.ListenAndServe(":9090", nil) // Definimos el puerto de escucha + if err != nil { + log.Fatal("ListenAndServe: ", err) + } + } +``` + +Aquí usamos el método `r.Methoc` para obtener el tipo de petición, el cual retornará uno de los siguientes verbos: "GET", "POST", "PUT", etc. + +En la función `login`, usamos `r.Method` para verificar si es una petición a la página o una petición para procesar los datos. En otras palabras, verificamos si el usuario solo abró la página o está intentando ingresar. Únicamente mostramos la página cuando viene desde un método GET, y ejecuta la lógica cuando viene un método POST. + +Podrás ver la interfaz cuando visitas `http://127.0.0.1:9090/login` en tu navegador. + +![](images/4.1.login.png?raw=true) + +Figure 4.1 Interfaz de ingreso de usuario + +El servidor no va a imprimir nada hasta que nosotros escribamos un usuario y contraseña, porque el manejador no analizará los datos hasta que llamemos `r.ParseForm()`. Agreguemos `r.ParseForm()` antes de `fmt.Println("username:", r.Form["username"])`, compilemos el programa e intentémolo de nuevo. Ahora puedes ve la información en la consola del servidor. + +`r.Form` contiene todos los argumentos de la petición, por ejemplo la cadena de consulta en la URL y la información en el POST y PUT. Si la información tiene conflictos, como parámetros que tienen el mismo nombre, el servidor almacenará la información en un segmento con múltiples valores. La documentación de Go dice que Go guardará la información de las peticiones GET y el POST en diferentes lugares. + +Trata de cambiar la URL de ingreso de `http://127.0.0.1:9090/login` a `http://127.0.0.1:9090/login?username=astaxie` en el archivo `login.gtpl`, prueba de nuevo, Y podrás ver el segmento en el lado del servidor. + +![](images/4.1.slice.png?raw=true) + +Figure 4.2 Server prints request data + +El tipo de `request.Form` es `url.Value`. Y lo guarda en el formato `llave=valor`. +``` + v := url.Values{} + v.Set("name", "Ava") + v.Add("friend", "Jess") + v.Add("friend", "Sarah") + v.Add("friend", "Zoe") + // v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe" + fmt.Println(v.Get("name")) + fmt.Println(v.Get("friend")) + fmt.Println(v["friend"]) +``` +**Recomendaciones** Las peticiones tienen la habilidad de acceder información por medio del método `FormValue()`. Por ejemplo, puedes cambiar de `r.Form["username"]` a `r.FormValue("username")`, y Go llamará `r.ParseForm` automáticamente. Nota que se retornará el primer valor, si existen varias llaves con el mismo nombre y retornará una cadena vacía si no existe el argumento. + +## Links + +- [Indice](preface.md) +- Sección anterior: [Formulario de entrada de los usuario](04.0.md) +- Siguiente sección: [Verificando las entradas](04.2.md) diff --git a/es/preface.md b/es/preface.md new file mode 100644 index 00000000..58da2dc1 --- /dev/null +++ b/es/preface.md @@ -0,0 +1,96 @@ +- 1.[Go environment configuration](01.0.md) + - 1.1. [Installation](01.1.md) + - 1.2. [$GOPATH and workspace](01.2.md) + - 1.3. [Go commands](01.3.md) + - 1.4. [Go development tools](01.4.md) + - 1.5. [Summary](01.5.md) +- 2.[Go basic knowledge](02.0.md) + - 2.1. ["Hello, Go"](02.1.md) + - 2.2. [Go foundation](02.2.md) + - 2.3. [Control statements and functions](02.3.md) + - 2.4. [struct](02.4.md) + - 2.5. [Object-oriented](02.5.md) + - 2.6. [interface](02.6.md) + - 2.7. [Concurrency](02.7.md) + - 2.8. [Summary](02.8.md) +- 3.[Web foundation](03.0.md) + - 3.1. [Web working principles](03.1.md) + - 3.2. [Build a simple web server](03.2.md) + - 3.3. [How Go works with web](03.3.md) + - 3.4. [Get into http package](03.4.md) + - 3.5. [Summary](03.5.md) +- 4.[User form](04.0.md) + - 4.1. [Process form inputs](04.1.md) + - 4.2. [Verification of inputs](04.2.md) + - 4.3. [Cross site scripting](04.3.md) + - 4.4. [Duplicate submissions](04.4.md) + - 4.5. [File upload](04.5.md) + - 4.6. [Summary](04.6.md) +- 5.[Database](05.0.md) + - 5.1. [database/sql interface](05.1.md) + - 5.2. [MySQL](05.2.md) + - 5.3. [SQLite](05.3.md) + - 5.4. [PostgreSQL](05.4.md) + - 5.5. [Develop ORM based on beedb](05.5.md) + - 5.6. [NoSQL database](05.6.md) + - 5.7. [Summary](05.7.md) +- 6.[Data storage and session](06.0.md) + - 6.1. [Session and cookies](06.1.md) + - 6.2. [How to use session in Go](06.2.md) + - 6.3. [Session storage](06.3.md) + - 6.4. [Prevent hijack of session](06.4.md) + - 6.5. [Summary](06.5.md) +- 7.[Text files](07.0.md) + - 7.1. [XML](07.1.md) + - 7.2. [JSON](07.2.md) + - 7.3. [Regexp](07.3.md) + - 7.4. [Templates](07.4.md) + - 7.5. [Files](07.5.md) + - 7.6. [Strings](07.6.md) + - 7.7. [Summary](07.7.md) +- 8.[Web services](08.0.md) + - 8.1. [Sockets](08.1.md) + - 8.2. [WebSocket](08.2.md) + - 8.3. [REST](08.3.md) + - 8.4. [RPC](08.4.md) + - 8.5. [Summary](08.5.md) +- 9.[Security and encryption](09.0.md) + - 9.1. [CSRF attacks](09.1.md) + - 9.2. [Filter inputs](09.2.md) + - 9.3. [XSS attacks](09.3.md) + - 9.4. [SQL injection](09.4.md) + - 9.5. [Password storage](09.5.md) + - 9.6. [Encrypt and decrypt data](09.6.md) + - 9.7. [Summary](09.7.md) +- 10.[Internationalization and localization](10.0.md) + - 10.1 [Time zone](10.1.md) + - 10.2 [Localized resources](10.2.md) + - 10.3 [International sites](10.3.md) + - 10.4 [Summary](10.4.md) +- 11.[Error handling, debugging and testing](11.0.md) + - 11.1. [Error handling](11.1.md) + - 11.2. [Debugging by using GDB](11.2.md) + - 11.3. [Write test cases](11.3.md) + - 11.4. [Summary](11.4.md) +- 12.[Deployment and maintenance](12.0.md) + - 12.1. [Logs](12.1.md) + - 12.2. [Errors and crashes](12.2.md) + - 12.3. [Deployment](12.3.md) + - 12.4. [Backup and recovery](12.4.md) + - 12.5. [Summary](12.5.md) +- 13.[Build a web framework](13.0.md) + - 13.1. [Project program](13.1.md) + - 13.2. [Customized routers](13.2.md) + - 13.3. [Design controllers](13.3.md) + - 13.4. [Logs and configurations](13.4.md) + - 13.5. [Add, delete and update blogs](13.5.md) + - 13.6. [Summary](13.6.md) +- 14.[Develop web framework](14.0.md) + - 14.1. [Static files](14.1.md) + - 14.2. [Session](14.2.md) + - 14.3. [Form](14.3.md) + - 14.4. [User validation](14.4.md) + - 14.5. [Multi-language support](14.5.md) + - 14.6. [pprof](14.6.md) + - 14.7. [Summary](14.7.md) +- Appendix A [References](ref.md) \ No newline at end of file diff --git a/zh/01.4.md b/zh/01.4.md index 33f94fc8..95e9f5e3 100644 --- a/zh/01.4.md +++ b/zh/01.4.md @@ -93,7 +93,7 @@ ## Sublime Text - 这里将介绍Sublime Text 2(以下简称Sublime)+GoSublime的组合,那么为什么选择这个组合呢? + 这里将介绍Sublime Text 3(以下简称Sublime)+GoSublime + gocode的组合,那么为什么选择这个组合呢? - 自动化提示代码,如下图所示 @@ -109,7 +109,7 @@ 图1.6 sublime项目管理界面 - 支持语法高亮 - - Sublime Text 2可免费使用,只是保存次数达到一定数量之后就会提示是否购买,点击取消继续用,和正式注册版本没有任何区别。 + - Sublime Text 3可免费使用,只是保存次数达到一定数量之后就会提示是否购买,点击取消继续用,和正式注册版本没有任何区别。 接下来就开始讲如何安装,下载[Sublime](http://www.sublimetext.com/) @@ -118,7 +118,16 @@ 1. 打开之后安装 Package Control:Ctrl+` 打开命令行,执行如下代码: - import urllib2,os; pf='Package Control.sublime-package'; ipp=sublime.installed_packages_path(); os.makedirs(ipp) if not os.path.exists(ipp) else None; urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler())); open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()); print 'Please restart Sublime Text to finish installation' + 适用于 Sublime Text 3: + +```Go + import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()) +``` + 适用于 Sublime Text 2: + +```Go + import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read());print('Please restart Sublime Text to finish installation') +``` 这个时候重启一下Sublime,可以发现在在菜单栏多了一个如下的栏目,说明Package Control已经安装成功了。 @@ -137,6 +146,22 @@ 这个时候输入GoSublime,按确定就开始安装了。同理应用于SidebarEnhancements和Go Build。 + 3. 安装[gocode](https://github.com/nsf/gocode/) + + go get -u github.com/nsf/gocode + + gocode 将会安装在默认`$GOBIN` + + 另外建议安装gotests(生成测试代码): + + + 先在sublime安装gotests插件,再运行: + + ```Go + go get -u -v github.com/cweill/gotests/... + + ``` + 3. 验证是否安装成功,你可以打开Sublime,打开main.go,看看语法是不是高亮了,输入`import`是不是自动化提示了,`import "fmt"`之后,输入`fmt.`是不是自动化提示有函数了。 如果已经出现这个提示,那说明你已经安装完成了,并且完成了自动提示。 @@ -153,6 +178,135 @@ ln -s /Applications/Sublime\ Text\ 2.app/Contents/SharedSupport/bin/subl /usr/local/bin/sublime +## Visual Studio Code + +vscode是微软基于Electron和web技术构建的开源编辑器, 是一款很强大的编辑器。开源地址:https://github.com/Microsoft/vscode + +1、安装Visual Studio Code 最新版 + +官方网站:https://code.visualstudio.com/ +下载Visual Studio Code 最新版,安装过程略。 + +2、安装Go插件 + +点击右边的Extensions图标 +搜索Go插件 +在插件列表中,选择 Go,进行安装,安装之后,系统会提示重启Visual Studio Code。 + +建议把自动保存功能开启。开启方法为:选择菜单File,点击Auto save。 + +vscode代码设置可用于Go扩展。这些都可以在用户的喜好来设置或工作区设置(.vscode/settings.json)。 + +打开首选项-用户设置settings.json: + +```Go + +{ + "go.buildOnSave": true, + "go.lintOnSave": true, + "go.vetOnSave": true, + "go.buildFlags": [], + "go.lintFlags": [], + "go.vetFlags": [], + "go.coverOnSave": false, + "go.useCodeSnippetsOnFunctionSuggest": false, + "go.formatOnSave": true, + //goimports + "go.formatTool": "goreturns", + "go.goroot": "",//你的Goroot + "go.gopath": "",//你的Gopath +} +``` + +接着安装依赖包支持(网络不稳定,请直接到Github[Golang](https://github.com/golang)下载再移动到相关目录): +```Go + go get -u -v github.com/nsf/gocode + go get -u -v github.com/rogpeppe/godef + go get -u -v github.com/zmb3/gogetdoc + go get -u -v github.com/golang/lint/golint + go get -u -v github.com/lukehoban/go-outline + go get -u -v sourcegraph.com/sqs/goreturns + go get -u -v golang.org/x/tools/cmd/gorename + go get -u -v github.com/tpng/gopkgs + go get -u -v github.com/newhook/go-symbols + go get -u -v golang.org/x/tools/cmd/guru + go get -u -v github.com/cweill/gotests/... +``` + +vscode还有一项很强大的功能就是断点调试,结合[delve](https://github.com/derekparker/delve)可以很好的进行Go代码调试 + +```Go + + go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv + + brew install go-delve/delve/delve(mac可选) + +``` +如果有问题再来一遍: +```Go + + go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv + +``` +注意:修改"dlv-cert"证书, 选择"显示简介"->"信任"->"代码签名" 修改为: 始终信任 + +打开首选项-工作区设置,配置launch.json: + +```Go +{ + "version": "0.2.0", + "configurations": [ + { + "name": "main.go", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${workspaceRoot}",//工作空间路径 + "env": {}, + "args": [], + "showLog": true + } + ] +} +``` + +## Atom + +Atom是Github基于Electron和web技术构建的开源编辑器, 是一款很漂亮强大的编辑器缺点是速度比较慢。 + +首先要先安装下Atom,下载地址: https://atom.io/ + +然后安装go-plus插件: + + go-plus是Atom上面的一款开源的go语言开发环境的的插件 + + 它需要依赖下面的go语言工具: +```Go + 1.autocomplete-go :gocode的代码自动提示 + 2.gofmt :使用goftm,goimports,goturns + 3.builder-go:go-install 和go-test,验证代码,给出建议 + 4.gometalinet-linter:goline,vet,gotype的检查 + 5.navigator-godef:godef + 6.tester-goo :go test + 7.gorename :rename + +``` +在Atom中的 Preference 中可以找到install菜单,输入 go-plus,然后点击安装(install) + +就会开始安装 go-plus , go-plus 插件会自动安装对应的依赖插件,如果没有安装对应的go的类库会自动运行: go get 安装。 + + +##Goglang + +Gogland是JetBrains公司推出的Go语言集成开发环境,是Idea Go插件是强化版。Gogland同样基于IntelliJ平台开发,支持JetBrains的插件体系。 + +目前正式版暂未发布。 + +下载地址:https://www.jetbrains.com/go/ + ## Vim Vim是从vi发展出来的一个文本编辑器, 代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。 diff --git a/zh/01.5.md b/zh/01.5.md index ae9d7968..115e6fd4 100644 --- a/zh/01.5.md +++ b/zh/01.5.md @@ -1,6 +1,6 @@ # 1.5 总结 -这一章中我们主要介绍了如何安装Go,Go可以通过三种方式安装:源码安装、标准包安装、第三方工具安装,安装之后我们需要配置我们的开发环境,然后介绍了如何配置本地的`$GOPATH`,通过设置`$GOPATH`之后读者就可以创建项目,接着介绍了如何来进行项目编译、应用安装等问题,这些需要用到很多Go命令,所以接着就介绍了一些Go的常用命令工具,包括编译、安装、格式化、测试等命令,最后介绍了Go的开发工具,目前有很多Go的开发工具:LiteIDE、sublime、VIM、Emacs、Eclipse、Idea等工具,读者可以根据自己熟悉的工具进行配置,希望能够通过方便的工具快速的开发Go应用。 +这一章中我们主要介绍了如何安装Go,Go可以通过三种方式安装:源码安装、标准包安装、第三方工具安装,安装之后我们需要配置我们的开发环境,然后介绍了如何配置本地的`$GOPATH`,通过设置`$GOPATH`之后读者就可以创建项目,接着介绍了如何来进行项目编译、应用安装等问题,这些需要用到很多Go命令,所以接着就介绍了一些Go的常用命令工具,包括编译、安装、格式化、测试等命令,最后介绍了Go的开发工具,目前有很多Go的开发工具:LiteIDE、Sublime、VSCode、Atom、Goglang、VIM、Emacs、Eclipse、Idea等工具,读者可以根据自己熟悉的工具进行配置,希望能够通过方便的工具快速的开发Go应用。 ## links * [目录]() diff --git a/zh/02.1.md b/zh/02.1.md index 5bc0e69f..18d770de 100644 --- a/zh/02.1.md +++ b/zh/02.1.md @@ -7,6 +7,7 @@ 这就像一个传统,在学习大部分语言之前,你先学会如何编写一个可以输出`hello world`的程序。 准备好了吗?Let's Go! +```Go package main @@ -15,7 +16,7 @@ func main() { fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい\n") } - +``` 输出如下: Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい diff --git a/zh/02.2.md b/zh/02.2.md index 9348534c..b2657426 100644 --- a/zh/02.2.md +++ b/zh/02.2.md @@ -7,29 +7,34 @@ Go语言里面定义变量有多种方式。 使用`var`关键字是Go最基本的定义变量方式,与C语言不同的是Go把变量类型放在变量名后面: +```Go //定义一个名称为“variableName”,类型为"type"的变量 var variableName type - +``` 定义多个变量 +```Go //定义三个类型都是“type”的变量 var vname1, vname2, vname3 type - +``` 定义变量并初始化值 +```Go //初始化“variableName”的变量为“value”值,类型是“type” var variableName type = value - +``` 同时初始化多个变量 +```Go /* 定义三个类型都是"type"的变量,并且分别初始化为相应的值 vname1为v1,vname2为v2,vname3为v3 */ var vname1, vname2, vname3 type= v1, v2, v3 - +``` 你是不是觉得上面这样的定义有点繁琐?没关系,因为Go语言的设计者也发现了,有一种写法可以让它变得简单一点。我们可以直接忽略类型声明,那么上面的代码变成这样了: +```Go /* 定义三个变量,它们分别初始化为相应的值 @@ -37,8 +42,9 @@ Go语言里面定义变量有多种方式。 然后Go会根据其相应值的类型来帮你初始化它们 */ var vname1, vname2, vname3 = v1, v2, v3 - +``` 你觉得上面的还是有些繁琐?好吧,我也觉得。让我们继续简化: +```Go /* 定义三个变量,它们分别初始化为相应的值 @@ -46,7 +52,7 @@ Go语言里面定义变量有多种方式。 编译器会根据初始化的值自动推导出相应的类型 */ vname1, vname2, vname3 := v1, v2, v3 - +``` 现在是不是看上去非常简洁了?`:=`这个符号直接取代了`var`和`type`,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用`var`方式来定义全局变量。 `_`(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。在这个例子中,我们将值`35`赋予`b`,并同时丢弃`34`: @@ -54,30 +60,33 @@ Go语言里面定义变量有多种方式。 _, b := 34, 35 Go对于已声明但未使用的变量会在编译阶段报错,比如下面的代码就会产生一个错误:声明了`i`但未使用。 +```Go package main func main() { var i int } - +``` ## 常量 所谓常量,也就是在程序编译阶段就确定下来的值,而程序在运行时无法改变该值。在Go程序中,常量可定义为数值、布尔值或字符串等类型。 它的语法如下: +```Go const constantName = value //如果需要,也可以明确指定常量的类型: const Pi float32 = 3.1415926 - +``` 下面是一些常量声明的例子: +```Go const Pi = 3.1415926 const i = 10000 const MaxThread = 10 const prefix = "astaxie_" - +``` Go 常量和一般程序语言不同的是,可以指定相当多的小数位数(例如200位), 若指定給float32自动缩短为32bit,指定给float64自动缩短为64bit,详情参考[链接](http://golang.org/ref/spec#Constants) @@ -86,6 +95,7 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位 ### Boolean 在Go中,布尔值的类型为`bool`,值是`true`或`false`,默认为`false`。 +```Go //示例代码 var isActive bool // 全局变量声明 @@ -95,7 +105,7 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位 valid := false // 简短声明 available = true // 赋值操作 } - +``` ### 数值类型 @@ -116,15 +126,17 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位 浮点数的类型有`float32`和`float64`两种(没有`float`类型),默认是`float64`。 这就是全部吗?No!Go还支持复数。它的默认类型是`complex128`(64位实数+64位虚数)。如果需要小一些的,也有`complex64`(32位实数+32位虚数)。复数的形式为`RE + IMi`,其中`RE`是实数部分,`IM`是虚数部分,而最后的`i`是虚数单位。下面是一个使用复数的例子: +```Go var c complex64 = 5+5i //output: (5+5i) fmt.Printf("Value is: %v", c) - +``` ### 字符串 我们在上一节中讲过,Go中的字符串都是采用`UTF-8`字符集编码。字符串是用一对双引号(`""`)或反引号(`` ` `` `` ` ``)括起来定义,它的类型是`string`。 +```Go //示例代码 var frenchHello string // 声明变量为字符串的一般方法 @@ -134,35 +146,39 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位 japaneseHello := "Konichiwa" // 同上 frenchHello = "Bonjour" // 常规赋值 } - +``` 在Go中字符串是不可变的,例如下面的代码编译时会报错:cannot assign to s[0] +```Go var s string = "hello" s[0] = 'c' - +``` 但如果真的想要修改怎么办呢?下面的代码可以实现: +```Go s := "hello" c := []byte(s) // 将字符串 s 转换为 []byte 类型 c[0] = 'c' s2 := string(c) // 再转换回 string 类型 fmt.Printf("%s\n", s2) - +``` Go中可以使用`+`操作符来连接两个字符串: +```Go s := "hello," m := " world" a := s + m fmt.Printf("%s\n", a) - +``` 修改字符串也可写为: +```Go s := "hello" s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作 fmt.Printf("%s\n", s) - +``` 如果要声明一个多行的字符串怎么办?可以通过`` ` ``来声明: m := `hello @@ -175,12 +191,13 @@ Go中可以使用`+`操作符来连接两个字符串: ### 错误类型 Go内置有一个`error`类型,专门用来处理错误信息,Go的`package`里面还专门有一个包`errors`来处理错误: +```Go err := errors.New("emit macho dwarf: elf header corrupted") if err != nil { fmt.Print(err) } - +``` ### Go数据底层的存储 下面这张图来源于[Russ Cox Blog](http://research.swtch.com/)中一篇介绍[Go数据结构](http://research.swtch.com/godata)的文章,大家可以看到这些基础类型底层都是分配了一块内存,然后存储了相应的值。 @@ -196,6 +213,7 @@ Go内置有一个`error`类型,专门用来处理错误信息,Go的`package` 在Go语言中,同时声明多个常量、变量,或者导入多个包时,可采用分组的方式进行声明。 例如下面的代码: +```Go import "fmt" import "os" @@ -207,8 +225,9 @@ Go内置有一个`error`类型,专门用来处理错误信息,Go的`package` var i int var pi float32 var prefix string - +``` 可以分组写成如下形式: +```Go import( "fmt" @@ -226,10 +245,11 @@ Go内置有一个`error`类型,专门用来处理错误信息,Go的`package` pi float32 prefix string ) - +``` ### iota枚举 Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采用,它默认开始值是0,const中每增加一行加1: +```Go const( x = iota // x == 0 @@ -251,7 +271,7 @@ Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采 d,e,f = iota,iota,iota //d=3,e=3,f=3 g //g = 4 ) - +``` >除非被显式设置为其它值或`iota`,每个`const`分组的第一个常量被默认设置为它的0值,第二及后续的常量被默认设置为它前面那个常量的值,如果前面那个常量的值是`iota`,则它也被设置为`iota`。 ### Go程序设计的一些规则 @@ -263,35 +283,39 @@ Go之所以会那么简洁,是因为它有一些默认的行为: ### array `array`就是数组,它的定义方式如下: +```Go var arr [n]type - +``` 在`[n]type`中,`n`表示数组的长度,`type`表示存储元素的类型。对数组的操作和其它语言类似,都是通过`[]`来进行读取或赋值: +```Go var arr [10]int // 声明了一个int类型的数组 arr[0] = 42 // 数组下标是从0开始的 arr[1] = 13 // 赋值操作 fmt.Printf("The first element is %d\n", arr[0]) // 获取数据,返回42 fmt.Printf("The last element is %d\n", arr[9]) //返回未赋值的最后一个元素,默认返回0 - +``` 由于长度也是数组类型的一部分,因此`[3]int`与`[4]int`是不同的类型,数组也就不能改变长度。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。如果要使用指针,那么就需要用到后面介绍的`slice`类型了。 数组可以使用另一种`:=`来声明 +```Go a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组 b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0 c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度 - +``` 也许你会说,我想数组里面的值还是数组,能实现吗?当然咯,Go支持嵌套数组,即多维数组。比如下面的代码就声明了一个二维数组: +```Go // 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素 doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} // 上面的声明可以简化,直接忽略内部的类型 easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} - +``` 数组的分配如下所示: ![](images/2.2.array.png?raw=true) @@ -304,15 +328,18 @@ Go之所以会那么简洁,是因为它有一些默认的行为: 在很多应用场景中,数组并不能满足我们的需求。在初始定义数组时,我们并不知道需要多大的数组,因此我们就需要“动态数组”。在Go里面这种数据结构叫`slice` `slice`并不是真正意义上的动态数组,而是一个引用类型。`slice`总是指向一个底层`array`,`slice`的声明也可以像`array`一样,只是不需要长度。 +```Go // 和声明array一样,只是少了长度 var fslice []int - +``` 接下来我们可以声明一个`slice`,并初始化数据,如下所示: +```Go slice := []byte {'a', 'b', 'c', 'd'} - +``` `slice`可以从一个数组或一个已经存在的`slice`中再次声明。`slice`通过`array[i:j]`来获取,其中`i`是数组的开始位置,`j`是结束位置,但不包含`array[j]`,它的长度是`j-i`。 +```Go // 声明一个含有10个元素元素类型为byte的数组 var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} @@ -327,7 +354,7 @@ Go之所以会那么简洁,是因为它有一些默认的行为: // b是数组ar的另一个slice b = ar[3:5] // b的元素是:ar[3]和ar[4] - +``` >注意`slice`和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用`...`自动计算长度,而声明`slice`时,方括号内没有任何字符。 它们的数据结构如下所示 @@ -343,6 +370,7 @@ slice有一些简便的操作 - 如果从一个数组里面直接获取`slice`,可以这样`ar[:]`,因为默认第一个序列是0,第二个是数组的长度,即等价于`ar[0:len(ar)]` 下面这个例子展示了更多关于`slice`的操作: +```Go // 声明一个数组 var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} @@ -360,17 +388,18 @@ slice有一些简便的操作 bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展,此时bSlice包含:d,e,f,g,h bSlice = aSlice[:] // bSlice包含所有aSlice的元素: d,e,f,g - +``` `slice`是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值,例如上面的`aSlice`和`bSlice`,如果修改了`aSlice`中元素的值,那么`bSlice`相对应的值也会改变。 从概念上面来说`slice`像一个结构体,这个结构体包含了三个元素: - 一个指针,指向数组中`slice`指定的开始位置 - 长度,即`slice`的长度 - 最大长度,也就是`slice`开始位置到数组的最后位置的长度 +```Go Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} Slice_a := Array_a[2:5] - +``` 上面代码的真正存储结构如下图所示 ![](images/2.2.slice2.png?raw=true) @@ -388,10 +417,11 @@ slice有一些简便的操作 但当`slice`中没有剩余空间(即`(cap-len) == 0`)时,此时将动态分配新的数组空间。返回的`slice`数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的`slice`则不受影响。 从Go1.2开始slice支持了三个参数的slice,之前我们一直采用这种方式在slice或者array基础上来获取一个slice +```Go var array [10]int slice := array[2:4] - +``` 这个例子里面slice的容量是8,新版本里面可以指定这个容量 slice = array[2:4:7] @@ -405,6 +435,7 @@ slice有一些简便的操作 `map`也就是Python中字典的概念,它的格式为`map[keyType]valueType` 我们看下面的代码,`map`的读取和设置也类似`slice`一样,通过`key`来操作,只是`slice`的`index`只能是`int`类型,而`map`多了很多类型,可以是`int`,可以是`string`及所有完全定义了`==`与`!=`操作的类型。 +```Go // 声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化 var numbers map[string]int @@ -416,6 +447,7 @@ slice有一些简便的操作 fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据 // 打印出来如:第三个数字是: 3 +``` 这个`map`就像我们平常看到的表格一样,左边列是`key`,右边列是值 @@ -430,6 +462,8 @@ slice有一些简便的操作 通过`delete`删除`map`的元素: +```Go + // 初始化一个字典 rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 } // map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true @@ -442,15 +476,16 @@ slice有一些简便的操作 delete(rating, "C") // 删除key为C的元素 - +``` 上面说过了,`map`也是一种引用类型,如果两个`map`同时指向一个底层,那么一个改变,另一个也相应的改变: +```Go m := make(map[string]string) m["Hello"] = "Bonjour" m1 := m m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了 - +``` ### make、new操作 `make`用于内建类型(`map`、`slice` 和`channel`)的内存分配。`new`用于各种类型的内存分配。 @@ -473,6 +508,7 @@ slice有一些简便的操作 ## 零值 关于“零值”,所指并非是空值,而是一种“变量未填充前”的默认值,通常为0。 此处罗列 部分类型 的 “零值” +```Go int 0 int8 0 @@ -486,6 +522,7 @@ slice有一些简便的操作 bool false string "" +``` ## links * [目录]() * 上一章: [你好,Go](<02.1.md>) diff --git a/zh/02.3.md b/zh/02.3.md index 71189b59..c642b983 100644 --- a/zh/02.3.md +++ b/zh/02.3.md @@ -6,14 +6,16 @@ `if`也许是各种编程语言中最常见的了,它的语法概括起来就是:如果满足条件就做某事,否则做另一件事。 Go里面`if`条件判断语句中不需要括号,如下代码所示 +```Go if x > 10 { fmt.Println("x is greater than 10") } else { fmt.Println("x is less than 10") } - +``` Go的`if`还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示 +```Go // 计算获取值x,然后根据x返回的大小,判断是否大于10。 if x := computedValue(); x > 10 { @@ -24,8 +26,9 @@ Go的`if`还有一个强大的地方就是条件判断语句里面允许声明 //这个地方如果这样调用就编译出错了,因为x是条件里面的变量 fmt.Println(x) - +``` 多个条件的时候如下所示: +```Go if integer == 3 { fmt.Println("The integer is equal to 3") @@ -34,10 +37,11 @@ Go的`if`还有一个强大的地方就是条件判断语句里面允许声明 } else { fmt.Println("The integer is greater than 3") } - +``` ### goto Go有`goto`语句——请明智地使用它。用`goto`跳转到必须在当前函数内定义的标签。例如假设这样一个循环: +```Go func myFunc() { i := 0 @@ -46,21 +50,24 @@ Go有`goto`语句——请明智地使用它。用`goto`跳转到必须在当前 i++ goto Here //跳转到Here去 } - +``` >标签名是大小写敏感的。 ### for Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读取数据,又可以当作`while`来控制逻辑,还能迭代操作。它的语法如下: +```Go for expression1; expression2; expression3 { //... } - +``` `expression1`、`expression2`和`expression3`都是表达式,其中`expression1`和`expression3`是变量声明或者函数调用返回值之类的,`expression2`是用来条件判断,`expression1`在循环开始之前调用,`expression3`在每轮循环结束之时调用。 一个例子比上面讲那么多更有用,那么我们看看下面的例子吧: +```Go package main + import "fmt" func main(){ @@ -71,25 +78,28 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 fmt.Println("sum is equal to ", sum) } // 输出:sum is equal to 45 - +``` 有些时候需要进行多个赋值操作,由于Go里面没有`,`操作符,那么可以使用平行赋值`i, j = i+1, j-1` 有些时候如果我们忽略`expression1`和`expression3`: +```Go sum := 1 for ; sum < 1000; { sum += sum } - +``` 其中`;`也可以省略,那么就变成如下的代码了,是不是似曾相识?对,这就是`while`的功能。 +```Go sum := 1 for sum < 1000 { sum += sum } - +``` 在循环里面有两个关键操作`break`和`continue` ,`break`操作是跳出当前循环,`continue`是跳过本次循环。当嵌套过深的时候,`break`可以配合标签使用,即跳转至标签所指定的位置,详细参考如下例子: +```Go for index := 10; index>0; index-- { if index == 5{ @@ -99,26 +109,29 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 } // break打印出来10、9、8、7、6 // continue打印出来10、9、8、7、6、4、3、2、1 - +``` `break`和`continue`还可以跟着标号,用来跳到多重循环中的外层循环 `for`配合`range`可以用于读取`slice`和`map`的数据: +```Go for k,v:=range map { fmt.Println("map's key:",k) fmt.Println("map's val:",v) } - +``` 由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用`_`来丢弃不需要的返回值 例如 +```Go for _, v := range map{ fmt.Println("map's val:", v) } - +``` ### switch 有些时候你需要写很多的`if-else`来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候`switch`就能很好的解决这个问题。它的语法如下 +```Go switch sExpr { case expr1: @@ -130,8 +143,9 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 default: other code } - +``` `sExpr`和`expr1`、`expr2`、`expr3`的类型必须一致。Go的`switch`非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;而如果`switch`没有表达式,它会匹配`true`。 +```Go i := 10 switch i { @@ -144,8 +158,9 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 default: fmt.Println("All I know is that i is an integer") } - +``` 在第5行中,我们把很多值聚合在了一个`case`里面,同时,Go里面`switch`默认相当于每个`case`最后带有`break`,匹配成功后不会自动向下执行其他case,而是跳出整个`switch`, 但是可以使用`fallthrough`强制执行后面的case代码。 +```Go integer := 6 switch integer { @@ -167,24 +182,26 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 default: fmt.Println("default case") } - +``` 上面的程序将输出 +```Go The integer was <= 6 The integer was <= 7 The integer was <= 8 default case - +``` ## 函数 函数是Go里面的核心设计,它通过关键字`func`来声明,它的格式如下: +```Go func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { //这里是处理逻辑代码 //返回多个值 return value1, value2 } - +``` 上面的代码我们看出 - 关键字`func`用来声明一个函数`funcName` @@ -196,8 +213,10 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 - 如果有返回值, 那么必须在函数的外层添加return语句 下面我们来看一个实际应用函数的例子(用来计算Max值) +```Go package main + import "fmt" // 返回a、b中最大值. @@ -220,15 +239,17 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在这直接调用它 } - +``` 上面这个里面我们可以看到`max`函数有两个参数,它们的类型都是`int`,那么第一个变量的类型可以省略(即 a,b int,而非 a int, b int),默认为离它最近的类型,同理多于2个同类型的变量或者返回值。同时我们注意到它的返回值就是一个类型,这个就是省略写法。 ### 多个返回值 Go语言比C更先进的特性,其中一点就是函数能够返回多个值。 我们直接上代码看例子 +```Go package main + import "fmt" //返回 A+B 和 A*B @@ -245,31 +266,37 @@ Go语言比C更先进的特性,其中一点就是函数能够返回多个值 fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) } - +``` 上面的例子我们可以看到直接返回了两个参数,当然我们也可以命名返回参数的变量,这个例子里面只是用了两个类型,我们也可以改成如下这样的定义,然后返回的时候不用带上变量名,因为直接在函数里面初始化了。但如果你的函数是导出的(首字母大写),官方建议:最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差。 +```Go func SumAndProduct(A, B int) (add int, Multiplied int) { add = A+B Multiplied = A*B return } - +``` ### 变参 Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参: +```Go func myfunc(arg ...int) {} +``` `arg ...int`告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是`int`。在函数体中,变量`arg`是一个`int`的`slice`: +```Go for _, n := range arg { fmt.Printf("And the number is: %d\n", n) } - +``` ### 传值与传指针 当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。 为了验证我们上面的说法,我们来看一个例子 +```Go package main + import "fmt" //简单的一个函数,实现了参数+1的操作 @@ -288,7 +315,7 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4" fmt.Println("x = ", x) // 应该输出"x = 3" } - +``` 看到了吗?虽然我们调用了`add1`函数,并且在`add1`中执行`a = a+1`操作,但是上面例子中`x`变量的值没有发生变化 理由很简单:因为当我们调用`add1`的时候,`add1`接收的参数其实是`x`的copy,而不是`x`本身。 @@ -296,8 +323,10 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 那你也许会问了,如果真的需要传这个`x`本身,该怎么办呢? 这就牵扯到了所谓的指针。我们知道,变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有`add1`函数知道`x`变量所在的地址,才能修改`x`变量的值。所以我们需要将`x`所在地址`&x`传入函数,并将函数的参数的类型由`int`改为`*int`,即改为指针类型,才能在函数中修改`x`变量的值。此时参数仍然是按copy传递的,只是copy的是一个指针。请看下面的例子 +```Go package main + import "fmt" //简单的一个函数,实现了参数+1的操作 @@ -316,7 +345,7 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4" fmt.Println("x = ", x) // 应该输出 "x = 4" } - +``` 这样,我们就达到了修改`x`的目的。那么到底传指针有什么好处呢? - 传指针使得多个函数能操作同一个对象。 @@ -325,6 +354,7 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 ### defer Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。如下代码所示,我们一般写打开一个资源是这样操作的: +```Go func ReadWrite() bool { file.Open("file") @@ -342,8 +372,9 @@ Go语言中有种不错的设计,即延迟(defer)语句,你可以在函 file.Close() return true } - +``` 我们看到上面有很多重复的代码,Go的`defer`有效解决了这个问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。在`defer`后指定的函数会在函数退出前调用。 +```Go func ReadWrite() bool { file.Open("file") @@ -356,13 +387,14 @@ Go语言中有种不错的设计,即延迟(defer)语句,你可以在函 } return true } - +``` 如果有很多调用`defer`,那么`defer`是采用后进先出模式,所以如下代码会输出`4 3 2 1 0` +```Go for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) } - +``` ### 函数作为值、类型 在Go中函数也是一种变量,我们可以通过`type`来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型 @@ -370,8 +402,10 @@ Go语言中有种不错的设计,即延迟(defer)语句,你可以在函 type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...]) 函数作为类型到底有什么好处呢?那就是可以把这个类型的函数当做值来传递,请看下面的例子 +```Go package main + import "fmt" type testInt func(int) bool // 声明了一个函数类型 @@ -410,7 +444,7 @@ Go语言中有种不错的设计,即延迟(defer)语句,你可以在函 even := filter(slice, isEven) // 函数当做值来传递了 fmt.Println("Even elements of slice are: ", even) } - +``` 函数当做值和类型在我们写一些通用接口的时候非常有用,通过上面例子我们看到`testInt`这个类型是一个函数类型,然后两个`filter`函数的参数和返回值与`testInt`类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常的灵活。 ### Panic和Recover @@ -424,6 +458,7 @@ Recover >是一个内建的函数,可以让进入令人恐慌的流程中的`goroutine`恢复过来。`recover`仅在延迟函数中有效。在正常的执行过程中,调用`recover`会返回`nil`,并且没有其它任何效果。如果当前的`goroutine`陷入恐慌,调用`recover`可以捕获到`panic`的输入值,并且恢复正常的执行。 下面这个函数演示了如何在过程中使用`panic` +```Go var user = os.Getenv("USER") @@ -432,8 +467,9 @@ Recover panic("no value for $USER") } } - +``` 下面这个函数检查作为其参数的函数在执行时是否会产生`panic`: +```Go func throwsPanic(f func()) (b bool) { defer func() { @@ -444,7 +480,7 @@ Recover f() //执行函数f,如果f中出现了panic,那么就可以恢复回来 return } - +``` ### `main`函数和`init`函数 Go里面有两个保留的函数:`init`函数(能够应用于所有的`package`)和`main`函数(只能应用于`package main`)。这两个函数在定义时不能有任何的参数和返回值。虽然一个`package`里面可以写任意多个`init`函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个`package`中每个文件只写一个`init`函数。 @@ -459,15 +495,17 @@ Go程序会自动调用`init()`和`main()`,所以你不需要在任何地方 ### import 我们在写Go代码的时候经常用到import这个命令用来导入包文件,而我们经常看到的方式参考如下: +```Go import( "fmt" ) - +``` 然后我们代码里面可以通过如下的方式调用 +```Go fmt.Println("hello world") - +``` 上面这个fmt是Go语言的标准库,其实是去`GOROOT`环境变量指定目录下去加载该模块,当然Go的import还支持如下两种方式来加载自己写的模块: 1. 相对路径 @@ -505,12 +543,13 @@ Go程序会自动调用`init()`和`main()`,所以你不需要在任何地方 3. _操作 这个操作经常是让很多人费解的一个操作符,请看下面这个import - +```Go + import ( "database/sql" _ "github.com/ziutek/mymysql/godrv" ) - +``` _操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。 diff --git a/zh/02.4.md b/zh/02.4.md index 5cdbad44..70570368 100644 --- a/zh/02.4.md +++ b/zh/02.4.md @@ -1,16 +1,19 @@ # 2.4 struct类型 ## struct Go语言中,也和C或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器。例如,我们可以创建一个自定义类型`person`代表一个人的实体。这个实体拥有属性:姓名和年龄。这样的类型我们称之`struct`。如下代码所示: +```Go type person struct { name string age int } +``` 看到了吗?声明一个struct如此简单,上面的类型包含有两个字段 - 一个string类型的字段name,用来保存用户名称这个属性 - 一个int类型的字段age,用来保存用户年龄这个属性 如何使用struct呢?请看下面的代码 +```Go type person struct { name string @@ -22,6 +25,7 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型 P.name = "Astaxie" // 赋值"Astaxie"给P的name属性. P.age = 25 // 赋值"25"给变量P的age属性 fmt.Printf("The person's name is %s", P.name) // 访问P的name属性. +``` 除了上面这种P的声明使用之外,还有另外几种声明使用方式: - 1.按照顺序提供初始化值 @@ -37,8 +41,10 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型 P := new(person) 下面我们看一个完整的使用struct的例子 +```Go package main + import "fmt" // 声明一个新的类型 @@ -81,15 +87,17 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型 fmt.Printf("Of %s and %s, %s is older by %d years\n", bob.name, paul.name, bp_Older.name, bp_diff) } - +``` ### struct的匿名字段 我们上面介绍了如何定义一个struct,定义的时候是字段名与其类型一一对应,实际上Go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。 当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。 让我们来看一个例子,让上面说的这些更具体化 +```Go package main + import "fmt" type Human struct { @@ -125,7 +133,7 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型 mark.weight += 60 fmt.Println("His weight is", mark.weight) } - +``` 图例如下: ![](images/2.4.student_struct.png?raw=true) @@ -133,13 +141,16 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型 图2.7 struct组合,Student组合了Human struct和string基本类型 我们看到Student访问属性age和name的时候,就像访问自己所有用的字段一样,对,匿名字段就是这样,能够实现字段的继承。是不是很酷啊?还有比这个更酷的呢,那就是student还能访问Human这个字段作为字段名。请看下面的代码,是不是更酷了。 +```Go mark.Human = Human{"Marcus", 55, 220} mark.Human.age -= 1 - +``` 通过匿名访问和修改字段相当的有用,但是不仅仅是struct字段哦,所有的内置类型和自定义类型都是可以作为匿名字段的。请看下面的例子 +```Go package main + import "fmt" type Skills []string @@ -175,7 +186,7 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型 jane.int = 3 fmt.Println("Her preferred number is", jane.int) } - +``` 从上面例子我们看出来struct不仅仅能够将struct作为匿名字段、自定义类型、内置类型都可以作为匿名字段,而且可以在相应的字段上面进行函数操作(如例子中的append)。 这里有一个问题:如果human里面有一个字段叫做phone,而student也有一个字段叫做phone,那么该怎么办呢? @@ -183,8 +194,10 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型 Go里面很简单的解决了这个问题,最外层的优先访问,也就是当你通过`student.phone`访问的时候,是访问student里面的字段,而不是human里面的字段。 这样就允许我们去重载通过匿名字段继承的一些字段,当然如果我们想访问重载后对应匿名类型里面的字段,可以通过匿名字段名来访问。请看下面的例子 +```Go package main + import "fmt" type Human struct { @@ -205,7 +218,7 @@ Go里面很简单的解决了这个问题,最外层的优先访问,也就是 // 如果我们要访问Human的phone字段 fmt.Println("Bob's personal phone is:", Bob.Human.phone) } - +``` ## links * [目录]() diff --git a/zh/02.5.md b/zh/02.5.md index 63d22239..671fc278 100644 --- a/zh/02.5.md +++ b/zh/02.5.md @@ -3,8 +3,10 @@ ## method 现在假设有这么一个场景,你定义了一个struct叫做长方形,你现在想要计算他的面积,那么按照我们一般的思路应该会用下面的方式来实现 +```Go package main + import "fmt" type Rectangle struct { @@ -21,7 +23,7 @@ fmt.Println("Area of r1 is: ", area(r1)) fmt.Println("Area of r2 is: ", area(r2)) } - +``` 这段代码可以计算出来长方形的面积,但是area()不是作为Rectangle的方法实现的(类似面向对象里面的方法),而是将Rectangle的对象(如r1,r2)作为参数传入函数计算面积的。 这样实现当然没有问题咯,但是当需要增加圆形、正方形、五边形甚至其它多边形的时候,你想计算他们的面积的时候怎么办啊?那就只能增加新的函数咯,但是函数名你就必须要跟着换了,变成`area_rectangle, area_circle, area_triangle...` @@ -49,8 +51,10 @@ method的语法如下: func (r ReceiverType) funcName(parameters) (results) 下面我们用最开始的例子用method来实现: +```Go package main + import ( "fmt" "math" @@ -85,7 +89,7 @@ method的语法如下: fmt.Println("Area of c2 is: ", c2.area()) } - +``` 在使用method的时候重要注意几点 @@ -104,10 +108,12 @@ method的语法如下: >值得说明的一点是,图示中method用虚线标出,意思是此处方法的Receiver是以值传递,而非引用传递,是的,Receiver还可以是指针, 两者的差别在于, 指针作为Receiver会对实例对象的内容发生操作,而普通类型作为Receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。后文对此会有详细论述。 那是不是method只能作用在struct上面呢?当然不是咯,他可以定义在任何你自定义的类型、内置类型、struct等各种类型上面。这里你是不是有点迷糊了,什么叫自定义类型,自定义类型不就是struct嘛,不是这样的哦,struct只是自定义类型里面一种比较特殊的类型而已,还有其他自定义类型申明,可以通过如下这样的申明来实现。 +```Go type typeName typeLiteral - +``` 请看下面这个申明自定义类型的代码 +```Go type ages int @@ -121,14 +127,16 @@ method的语法如下: ... "December":31, } - +``` 看到了吗?简单的很吧,这样你就可以在自己的代码里面定义有意义的类型了,实际上只是一个定义了一个别名,有点类似于c中的typedef,例如上面ages替代了int 好了,让我们回到`method` 你可以在任何的自定义类型中定义任意多的`method`,接下来让我们看一个复杂一点的例子 +```Go package main + import "fmt" const( @@ -200,7 +208,7 @@ method的语法如下: fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String()) } - +``` 上面的代码通过const定义了一些常量,然后定义了一些自定义类型 - Color作为byte的别名 @@ -242,8 +250,10 @@ method的语法如下: ### method继承 前面一章我们学习了字段的继承,那么你也会发现Go的一个神奇之处,method也是可以继承的。如果匿名字段实现了一个method,那么包含这个匿名字段的struct也能调用该method。让我们来看下面这个例子 +```Go package main + import "fmt" type Human struct { @@ -274,11 +284,13 @@ method的语法如下: mark.SayHi() sam.SayHi() } - +``` ### method重写 上面的例子中,如果Employee想要实现自己的SayHi,怎么办?简单,和匿名字段冲突一样的道理,我们可以在Employee上面定义一个method,重写了匿名字段的方法。请看下面的例子 +```Go package main + import "fmt" type Human struct { @@ -315,7 +327,7 @@ method的语法如下: mark.SayHi() sam.SayHi() } - +``` 上面的代码设计的是如此的美妙,让人不自觉的为Go的设计惊叹! 通过这些内容,我们可以设计出基本的面向对象的程序了,但是Go里面的面向对象是如此的简单,没有任何的私有、公有关键字,通过大小写来实现(大写开头的为公有,小写开头的为私有),方法也同样适用这个原则。 diff --git a/zh/02.6.md b/zh/02.6.md index 57b38fd0..883d0f9a 100644 --- a/zh/02.6.md +++ b/zh/02.6.md @@ -14,6 +14,7 @@ Go语言里面设计最精妙的应该算interface,它让面向对象,内容 上面这些方法的组合称为interface(被对象Student和Employee实现)。例如Student和Employee都实现了interface:SayHi和Sing,也就是这两个对象是该interface类型。而Employee没有实现这个interface:SayHi、Sing和BorrowMoney,因为Employee没有实现BorrowMoney这个方法。 ### interface类型 interface类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。详细的语法参考下面这个例子 +```Go type Human struct { name string @@ -82,7 +83,7 @@ interface类型定义了一组方法,如果某个对象实现了某个接口 Sing(song string) SpendSalary(amount float32) } - +``` 通过上面的代码我们可以知道,interface可以被任意的对象实现。我们看到上面的Men interface被Human、Student和Employee实现。同理,一个对象可以实现任意多个interface,例如上面的Student实现了Men和YoungChap两个interface。 最后,任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含0个method的interface。 @@ -93,8 +94,10 @@ interface类型定义了一组方法,如果某个对象实现了某个接口 因为m能够持有这三种类型的对象,所以我们可以定义一个包含Men类型元素的slice,这个slice可以被赋予实现了Men接口的任意结构的对象,这个和我们传统意义上面的slice有所不同。 让我们来看一下下面这个例子: +```Go package main + import "fmt" type Human struct { @@ -169,11 +172,12 @@ interface类型定义了一组方法,如果某个对象实现了某个接口 value.SayHi() } } - +``` 通过上面的代码,你会发现interface就是一组抽象方法的集合,它必须由其他非interface类型实现,而不能自我实现, Go通过interface实现了duck-typing:即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。 ### 空interface 空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。 +```Go // 定义a为空接口 var a interface{} @@ -182,17 +186,20 @@ interface类型定义了一组方法,如果某个对象实现了某个接口 // a可以存储任意类型的数值 a = i a = s - +``` 一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是很有用啊! ### interface函数参数 interface的变量可以持有任意实现该interface类型的对象,这给我们编写函数(包括method)提供了一些额外的思考,我们是不是可以通过定义interface参数,让函数接受各种类型的参数。 举个例子:fmt.Println是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件,你会看到这样一个定义: +```Go type Stringer interface { String() string } +``` 也就是说,任何实现了String方法的类型都能作为参数被fmt.Println调用,让我们来试一试 +```Go package main import ( @@ -215,12 +222,14 @@ interface的变量可以持有任意实现该interface类型的对象,这给 Bob := Human{"Bob", 39, "000-7777-XXX"} fmt.Println("This Human is : ", Bob) } +``` 现在我们再回顾一下前面的Box示例,你会发现Color结构也定义了一个method:String。其实这也是实现了fmt.Stringer这个interface,即如果需要某个类型能被fmt包以特殊的格式输出,你就必须实现Stringer这个接口。如果没有实现这个接口,fmt将以默认的方式输出。 +```Go //实现同样的功能 fmt.Println("The biggest one is", boxes.BiggestsColor().String()) fmt.Println("The biggest one is", boxes.BiggestsColor()) - +``` 注:实现了error接口的对象(即实现了Error() string的对象),使用fmt输出时,会调用Error()方法,因此不必再定义String()方法了。 ### interface变量存储的类型 @@ -233,6 +242,7 @@ interface的变量可以持有任意实现该interface类型的对象,这给 如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。 让我们通过一个例子来更加深入的理解。 +```Go package main @@ -272,13 +282,14 @@ interface的变量可以持有任意实现该interface类型的对象,这给 } } } - +``` 是不是很简单啊,同时你是否注意到了多个if里面,还记得我前面介绍流程时讲过,if里面允许初始化变量。 也许你注意到了,我们断言的类型越多,那么if else也就越多,所以才引出了下面要介绍的switch。 - switch测试 最好的讲解就是代码例子,现在让我们重写上面的这个实现 +```Go package main @@ -319,21 +330,23 @@ interface的变量可以持有任意实现该interface类型的对象,这给 } } } - +``` 这里有一点需要强调的是:`element.(type)`语法不能在switch外的任何逻辑里面使用,如果你要在switch外面判断一个类型就使用`comma-ok`。 ### 嵌入interface Go里面真正吸引人的是它内置的逻辑语法,就像我们在学习Struct时学习的匿名字段,多么的优雅啊,那么相同的逻辑引入到interface里面,那不是更加完美了。如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。 我们可以看到源码包container/heap里面有这样的一个定义 +```Go type Interface interface { sort.Interface //嵌入字段sort.Interface Push(x interface{}) //a Push method to push elements into the heap Pop() interface{} //a Pop elements that pops elements from the heap } - +``` 我们看到sort.Interface其实就是嵌入字段,把sort.Interface的所有method给隐式的包含进来了。也就是下面三个方法: +```Go type Interface interface { // Len is the number of elements in the collection. @@ -344,49 +357,55 @@ Go里面真正吸引人的是它内置的逻辑语法,就像我们在学习Str // Swap swaps the elements with indexes i and j. Swap(i, j int) } - +``` 另一个例子就是io包下面的 io.ReadWriter ,它包含了io包下面的Reader和Writer两个interface: +```Go // io.ReadWriter type ReadWriter interface { Reader Writer } - +``` ### 反射 Go语言实现了反射,所谓反射就是能检查程序在运行时的状态。我们一般用到的包是reflect包。如何运用reflect包,官方的这篇文章详细的讲解了reflect包的实现原理,[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html) 使用reflect一般分成三步,下面简要的讲解一下:要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如下: +```Go t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素 v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值 - +``` 转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如 +```Go tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签 name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值 - +``` 获取反射值能返回相应的类型和数值 +```Go var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float()) - +``` 最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误 +```Go var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1) - +``` 如果要修改相应的值,必须这样写 +```Go var x float64 = 3.4 p := reflect.ValueOf(&x) v := p.Elem() v.SetFloat(7.1) - +``` 上面只是对反射的简单介绍,更深入的理解还需要自己在编程中不断的实践。 ## links diff --git a/zh/02.7.md b/zh/02.7.md index 7c7fc0b1..f6146e92 100644 --- a/zh/02.7.md +++ b/zh/02.7.md @@ -7,10 +7,12 @@ goroutine是Go并行设计的核心。goroutine说到底其实就是线程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。 goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`go`关键字实现了,其实就是一个普通的函数。 +```Go go hello(a, b, c) - +``` 通过关键字go就启动了一个goroutine。我们来看一个例子 +```Go package main @@ -41,7 +43,7 @@ goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`g // hello // world // hello - +``` 我们可以看到go关键字很方便的就实现了并发编程。 上面的多个goroutine运行在同一个进程里面,共享内存数据,不过设计上我们要遵循:不要通过共享来通信,而要通过通信来共享。 @@ -51,17 +53,20 @@ goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`g ## channels goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make 创建channel: +```Go ci := make(chan int) cs := make(chan string) cf := make(chan interface{}) - +``` channel通过操作符`<-`来接收和发送数据 +```Go ch <- v // 发送v到channel ch. v := <-ch // 从ch中接收数据,并赋值给v - +``` 我们把这些应用到我们的例子中来: +```Go package main @@ -85,18 +90,19 @@ channel通过操作符`<-`来接收和发送数据 fmt.Println(x, y, x + y) } - +``` 默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。 ## Buffered Channels 上面我们介绍了默认的非缓存类型的channel,不过Go也允许指定channel的缓冲大小,很简单,就是channel可以存储多少元素。ch:= make(chan bool, 4),创建了可以存储4个元素的bool 型channel。在这个channel 中,前4个元素可以无阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel 中读取一些元素,腾出空间。 +```Go ch := make(chan type, value) - +``` 当 value = 0 时,channel 是无缓冲阻塞读写的,当value > 0 时,channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。 我们看一下下面这个例子,你可以在自己本机测试一下,修改相应的value值 - +```Go package main @@ -111,9 +117,10 @@ channel通过操作符`<-`来接收和发送数据 } //修改为1报如下的错误: //fatal error: all goroutines are asleep - deadlock! - +``` ## Range和Close 上面这个例子中,我们需要读取两次c,这样不是很方便,Go考虑到了这一点,所以也可以通过range,像操作slice或者map一样操作缓存类型的channel,请看下面的例子 +```Go package main @@ -137,7 +144,7 @@ channel通过操作符`<-`来接收和发送数据 fmt.Println(i) } } - +``` `for i := range c`能够不断的读取channel里面的数据,直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel,生产者通过内置函数`close`关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法`v, ok := <-ch`测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。 >记住应该在生产者的地方关闭channel,而不是消费的地方去关闭它,这样容易引起panic @@ -148,6 +155,7 @@ channel通过操作符`<-`来接收和发送数据 我们上面介绍的都是只有一个channel的情况,那么如果存在多个channel的时候,我们该如何操作呢,Go里面提供了一个关键字`select`,通过`select`可以监听channel上的数据流动。 `select`默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。 +```Go package main @@ -177,8 +185,9 @@ channel通过操作符`<-`来接收和发送数据 }() fibonacci(c, quit) } - +``` 在`select`里面还有default语法,`select`其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。 +```Go select { case i := <-c: @@ -186,9 +195,10 @@ channel通过操作符`<-`来接收和发送数据 default: // 当c阻塞的时候执行这里 } - +``` ## 超时 有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时,通过如下的方式实现: +```Go func main() { c := make(chan int) @@ -207,7 +217,7 @@ channel通过操作符`<-`来接收和发送数据 }() <- o } - +``` ## runtime goroutine runtime包中有几个处理goroutine的函数: diff --git a/zh/02.8.md b/zh/02.8.md index a0ec6bb9..ca82b482 100644 --- a/zh/02.8.md +++ b/zh/02.8.md @@ -1,13 +1,14 @@ # 2.8 总结 这一章我们主要介绍了Go语言的一些语法,通过语法我们可以发现Go是多么的简单,只有二十五个关键字。让我们再来回顾一下这些关键字都是用来干什么的。 +```Go break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var - +``` - var和const参考2.2Go语言基础里面的变量和常量申明 - package和import已经有过短暂的接触 - func 用于定义函数和方法 diff --git a/zh/03.2.md b/zh/03.2.md index f926aeef..ee59af4a 100644 --- a/zh/03.2.md +++ b/zh/03.2.md @@ -3,6 +3,7 @@ 前面小节已经介绍了Web是基于http协议的一个服务,Go语言里面提供了一个完善的net/http包,通过http包可以很方便的就搭建起来一个可以运行的Web服务。同时使用这个包能很简单地对Web的路由,静态文件,模版,cookie等数据进行设置和操作。 ## http包建立Web服务器 +```Go package main @@ -33,7 +34,8 @@ log.Fatal("ListenAndServe: ", err) } } - + +``` 上面这个代码,我们build之后,然后执行web.exe,这个时候其实已经在9090端口监听http链接请求了。 在浏览器输入`http://localhost:9090` diff --git a/zh/03.3.md b/zh/03.3.md index a0beed81..ceab0656 100644 --- a/zh/03.3.md +++ b/zh/03.3.md @@ -36,6 +36,7 @@ Handler:处理请求和生成返回信息的处理逻辑 前面小节的代码里面我们可以看到,Go是通过一个函数`ListenAndServe`来处理这些事情的,这个底层其实这样处理的:初始化一个server对象,然后调用了`net.Listen("tcp", addr)`,也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。 下面代码来自Go的http包的源码,通过下面的代码我们可以看到整个的http处理过程: +```Go func (srv *Server) Serve(l net.Listener) error { defer l.Close() @@ -67,6 +68,7 @@ Handler:处理请求和生成返回信息的处理逻辑 } } +``` 监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了`srv.Serve(net.Listener)`函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个`for{}`,首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:`go c.serve()`。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。 那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request:`c.readRequest()`,然后获取相应的handler:`handler := c.server.Handler`,也就是我们刚才在调用函数`ListenAndServe`时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取`handler = DefaultServeMux`,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了`http.HandleFunc("/", sayhelloName)`嘛。这个作用就是注册了请求`/`的路由规则,当请求uri为"/",路由就会转到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。 diff --git a/zh/03.4.md b/zh/03.4.md index 3d502277..a76b9a01 100644 --- a/zh/03.4.md +++ b/zh/03.4.md @@ -7,6 +7,7 @@ Go的http有两个核心功能:Conn、ServeMux 与我们一般编写的http服务器不同, Go为了实现高并发和高性能, 使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。 Go在等待客户端请求里面是这样写的: +```Go c, err := srv.newConn(rw) if err != nil { @@ -14,12 +15,14 @@ Go在等待客户端请求里面是这样写的: } go c.serve() +``` 这里我们可以看到客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到对应的handler,该handler中便可以读取到相应的header信息,这样保证了每个请求的独立性。 ## ServeMux的自定义 我们前面小节讲述conn.server的时候,其实内部是调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢? 它的结构如下: +```Go type ServeMux struct { mu sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制 @@ -27,7 +30,9 @@ Go在等待客户端请求里面是这样写的: hosts bool // 是否在任意的规则中带有host信息 } +``` 下面看一下muxEntry +```Go type muxEntry struct { explicit bool // 是否精确匹配 @@ -35,13 +40,17 @@ Go在等待客户端请求里面是这样写的: pattern string //匹配字符串 } +``` 接着看一下Handler的定义 +```Go type Handler interface { ServeHTTP(ResponseWriter, *Request) // 路由实现器 } +``` Handler是一个接口,但是前一小节中的`sayhelloName`函数并没有实现ServeHTTP这个接口,为什么能添加呢?原来在http包里面还定义了一个类型`HandlerFunc`,我们定义的函数`sayhelloName`就是这个HandlerFunc调用之后的结果,这个类型默认就实现了ServeHTTP这个接口,即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。 +```Go type HandlerFunc func(ResponseWriter, *Request) @@ -49,8 +58,9 @@ Handler是一个接口,但是前一小节中的`sayhelloName`函数并没有 func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } - +``` 路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?请看下面的代码,默认的路由器实现了`ServeHTTP`: +```Go func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { @@ -61,10 +71,11 @@ Handler是一个接口,但是前一小节中的`sayhelloName`函数并没有 h, _ := mux.Handler(r) h.ServeHTTP(w, r) } - +``` 如上所示路由器接收到请求之后,如果是`*`那么关闭链接,不然调用`mux.Handler(r)`返回对应设置路由的处理Handler,然后执行`h.ServeHTTP(w, r)` 也就是调用对应路由的handler的ServerHTTP接口,那么mux.Handler(r)怎么处理的呢? +```Go func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { if r.Method != "CONNECT" { @@ -92,12 +103,13 @@ Handler是一个接口,但是前一小节中的`sayhelloName`函数并没有 } return } - +``` 原来他是根据用户请求的URL和路由器里面存储的map去匹配的,当匹配到之后返回存储的handler,调用这个handler的ServeHTTP接口就可以执行到相应的函数了。 通过上面这个介绍,我们了解了整个路由过程,Go其实支持外部实现的路由器 `ListenAndServe`的第二个参数就是用以配置外部路由器的,它是一个Handler接口,即外部路由器只要实现了Handler接口就可以,我们可以在自己实现的路由器的ServeHTTP里面实现自定义路由功能。 如下代码所示,我们自己实现了一个简易的路由器 +```Go package main @@ -126,7 +138,7 @@ Handler是一个接口,但是前一小节中的`sayhelloName`函数并没有 mux := &MyMux{} http.ListenAndServe(":9090", mux) } - +``` ## Go代码的执行流程 通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。 diff --git a/zh/04.1.md b/zh/04.1.md index 1f364713..bb1ab16b 100644 --- a/zh/04.1.md +++ b/zh/04.1.md @@ -1,6 +1,7 @@ # 4.1 处理表单的输入 先来看一个表单递交的例子,我们有如下的表单内容,命名成文件login.gtpl(放入当前新建项目的目录里面) +```html @@ -14,11 +15,11 @@ - +``` 上面递交表单到服务器的`/login`,当用户输入信息点击登陆之后,会跳转到服务器的路由`login`里面,我们首先要判断这个是什么方式传递过来,POST还是GET呢? http包里面有一个很简单的方式就可以获取,我们在前面web的例子的基础上来看看怎么处理login页面的form数据 - +```Go package main @@ -65,7 +66,7 @@ http包里面有一个很简单的方式就可以获取,我们在前面web的 } } - +``` 通过上面的代码我们可以看出获取请求方法是通过`r.Method`来完成的,这是个字符串类型的变量,返回GET, POST, PUT等method信息。 login函数中我们根据`r.Method`来判断是显示登录界面还是处理登录逻辑。当GET方式请求时显示登录界面,其他方式请求时则处理登录逻辑,如查询数据库、验证登录信息等。 @@ -89,6 +90,7 @@ login函数中我们根据`r.Method`来判断是显示登录界面还是处理 图4.2 服务器端打印接受到的信息 `request.Form`是一个url.Values类型,里面存储的是对应的类似`key=value`的信息,下面展示了可以对form数据进行的一些操作: +```Go v := url.Values{} v.Set("name", "Ava") @@ -99,7 +101,8 @@ login函数中我们根据`r.Method`来判断是显示登录界面还是处理 fmt.Println(v.Get("name")) fmt.Println(v.Get("friend")) fmt.Println(v["friend"]) - + +``` >**Tips**: Request本身也提供了FormValue()函数来获取用户提交的参数。如r.Form["username"]也可写成r.FormValue("username")。调用r.FormValue时会自动调用r.ParseForm,所以不必提前调用。r.FormValue只会返回同名参数中的第一个,若参数不存在则返回空字符串。 diff --git a/zh/04.2.md b/zh/04.2.md index d9e8a96a..3103c74e 100644 --- a/zh/04.2.md +++ b/zh/04.2.md @@ -6,17 +6,19 @@ ## 必填字段 你想要确保从一个表单元素中得到一个值,例如前面小节里面的用户名,我们如何处理呢?Go有一个内置函数`len`可以获取字符串的长度,这样我们就可以通过len来获取数据的长度,例如: +```Go if len(r.Form["username"][0])==0{ //为空的处理 } - +``` `r.Form`对不同类型的表单元素的留空有不同的处理, 对于空文本框、空文本区域以及文件上传,元素的值为空值,而如果是未选中的复选框和单选按钮,则根本不会在r.Form中产生相应条目,如果我们用上面例子中的方式去获取数据时程序就会报错。所以我们需要通过`r.Form.Get()`来获取值,因为如果字段不存在,通过该方式获取的是空值。但是通过`r.Form.Get()`只能获取单个的值,如果是map的值,必须通过上面的方式来获取。 ## 数字 你想要确保一个表单输入框中获取的只能是数字,例如,你想通过表单获取某个人的具体年龄是50岁还是10岁,而不是像“一把年纪了”或“年轻着呢”这种描述 如果我们是判断正整数,那么我们先转化成int类型,然后进行处理 +```Go getint,err:=strconv.Atoi(r.Form.Get("age")) if err!=nil{ @@ -27,36 +29,40 @@ if getint >100 { //太大了 } - +``` 还有一种方式就是正则匹配的方式 +```Go if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m { return false } - +``` 对于性能要求很高的用户来说,这是一个老生常谈的问题了,他们认为应该尽量避免使用正则表达式,因为使用正则表达式的速度会比较慢。但是在目前机器性能那么强劲的情况下,对于这种简单的正则表达式效率和类型转换函数是没有什么差别的。如果你对正则表达式很熟悉,而且你在其它语言中也在使用它,那么在Go里面使用正则表达式将是一个便利的方式。 >Go实现的正则是[RE2](http://code.google.com/p/re2/wiki/Syntax),所有的字符都是UTF-8编码的。 ## 中文 有时候我们想通过表单元素获取一个用户的中文名字,但是又为了保证获取的是正确的中文,我们需要进行验证,而不是用户随便的一些输入。对于中文我们目前有两种方式来验证,可以使用 `unicode` 包提供的 `func Is(rangeTab *RangeTable, r rune) bool` 来验证,也可以使用正则方式来验证,这里使用最简单的正则方式,如下代码所示 +```Go if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m { return false } - +``` ## 英文 我们期望通过表单元素获取一个英文值,例如我们想知道一个用户的英文名,应该是astaxie,而不是asta谢。 我们可以很简单的通过正则验证数据: +```Go if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m { return false } - +``` ## 电子邮件地址 你想知道用户输入的一个Email地址是否正确,通过如下这个方式可以验证: +```Go if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m { fmt.Println("no") @@ -64,26 +70,29 @@ fmt.Println("yes") } - +``` ## 手机号码 你想要判断用户输入的手机号码是否正确,通过正则也可以验证: +```Go if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m { return false } - +``` ## 下拉菜单 如果我们想要判断表单里面` - +``` 那么我们可以这样来验证 +```Go slice:=[]string{"apple","pear","banane"} @@ -95,14 +104,16 @@ } return false - +``` ## 单选按钮 如果我们想要判断radio按钮是否有一个被选中了,我们页面的输出可能就是一个男、女性别的选择,但是也可能一个15岁大的无聊小孩,一手拿着http协议的书,另一只手通过telnet客户端向你的程序在发送请求呢,你设定的性别男值是1,女是2,他给你发送一个3,你的程序会出现异常吗?因此我们也需要像下拉菜单的判断方式类似,判断我们获取的值是我们预设的值,而不是额外的值。 +```html 女 - +``` 那我们也可以类似下拉菜单的做法一样 +```Go slice:=[]int{1,2} @@ -112,15 +123,17 @@ } } return false - +``` ## 复选框 有一项选择兴趣的复选框,你想确定用户选中的和你提供给用户选择的是同一个类型的数据。 +```html 足球 篮球 网球 - +``` 对于复选框我们的验证和单选有点不一样,因为接收到的数据是一个slice +```Go slice:=[]string{"football","basketball","tennis"} a:=Slice_diff(r.Form["interest"],slice) @@ -129,7 +142,7 @@ } return false - +``` 上面这个函数`Slice_diff`包含在我开源的一个库里面(操作slice和map的库),[https://github.com/astaxie/beeku](https://github.com/astaxie/beeku) ## 日期和时间 @@ -137,14 +150,16 @@ ,用户在日程表中安排8月份的第45天开会,或者提供未来的某个时间作为生日。 Go里面提供了一个time的处理包,我们可以把用户的输入年月日转化成相应的时间,然后进行逻辑判断 +```Go t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) fmt.Printf("Go launched at %s\n", t.Local()) - +``` 获取time之后我们就可以进行很多时间函数的操作。具体的判断就根据自己的需求调整。 ## 身份证号码 如果我们想验证表单输入的是否是身份证,通过正则也可以方便的验证,但是身份证有15位和18位,我们两个都需要验证 +```Go //验证15位身份证,15位的是全部数字 if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m { @@ -155,7 +170,8 @@ Go里面提供了一个time的处理包,我们可以把用户的输入年月 if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m { return false } - + +``` 上面列出了我们一些常用的服务器端的表单元素验证,希望通过这个引导入门,能够让你对Go的数据验证有所了解,特别是Go里面的正则处理。 ## links diff --git a/zh/04.3.md b/zh/04.3.md index e0c350e2..42c458ee 100644 --- a/zh/04.3.md +++ b/zh/04.3.md @@ -14,11 +14,12 @@ 我们看4.1小节的例子 +```Go fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端 fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端 - +``` 如果我们输入的username是``,那么我们可以在浏览器上面看到输出如下所示: ![](images/4.3.escape.png?raw=true) @@ -26,23 +27,25 @@ 图4.3 Javascript过滤之后的输出 Go的html/template包默认帮你过滤了html标签,但是有时候你只想要输出这个``看起来正常的信息,该怎么处理?请使用text/template。请看下面的例子: +```Go import "text/template" ... t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) err = t.ExecuteTemplate(out, "T", "") - +``` 输出 Hello, ! 或者使用template.HTML类型 +```Go import "html/template" ... t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) err = t.ExecuteTemplate(out, "T", template.HTML("")) - +``` 输出 Hello, ! @@ -50,12 +53,13 @@ Go的html/template包默认帮你过滤了html标签,但是有时候你只想 转换成`template.HTML`后,变量的内容也不会被转义 转义的例子: +```Go import "html/template" ... t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) err = t.ExecuteTemplate(out, "T", "") - +``` 转义之后的输出: Hello, <script>alert('you have been pwned')</script>! diff --git a/zh/04.4.md b/zh/04.4.md index bbed11e9..85c5500f 100644 --- a/zh/04.4.md +++ b/zh/04.4.md @@ -5,6 +5,7 @@ 解决方案是在表单中添加一个带有唯一值的隐藏字段。在验证表单时,先检查带有该惟一值的表单是否已经递交过了。如果是,拒绝再次递交;如果不是,则处理表单进行逻辑处理。另外,如果是采用了Ajax模式递交表单的话,当表单递交后,通过javascript来禁用表单的递交按钮。 我继续拿4.2小节的例子优化: +```html 足球 篮球 @@ -13,8 +14,9 @@ 密码: - +``` 我们在模版里面增加了一个隐藏字段`token`,这个值我们通过MD5(时间戳)来获取惟一值,然后我们把这个值存储到服务器端(session来控制,我们将在第六章讲解如何保存),以方便表单提交时比对判定。 +```Go func login(w http.ResponseWriter, r *http.Request) { fmt.Println("method:", r.Method) //获取请求的方法 @@ -41,7 +43,7 @@ template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端 } } - +``` 上面的代码输出到页面的源码如下: ![](images/4.4.token.png?raw=true) diff --git a/zh/04.5.md b/zh/04.5.md index 4e8ff246..4f71bd89 100644 --- a/zh/04.5.md +++ b/zh/04.5.md @@ -2,12 +2,14 @@ 你想处理一个由用户上传的文件,比如你正在建设一个类似Instagram的网站,你需要存储用户拍摄的照片。这种需求该如何实现呢? 要使表单能够上传文件,首先第一步就是要添加form的`enctype`属性,`enctype`属性有如下三种情况: +``` application/x-www-form-urlencoded 表示在发送前编码所有字符(默认) multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。 text/plain 空格转换为 "+" 加号,但不对特殊字符编码。 - +``` 所以,创建新的表单html文件, 命名为upload.gtpl, html代码应该类似于: +```html @@ -21,8 +23,9 @@ - +``` 在服务器端,我们增加一个handlerFunc: +```Go http.HandleFunc("/upload", upload) @@ -55,7 +58,7 @@ io.Copy(f, file) } } - +``` 通过上面的代码可以看到,处理文件上传我们需要调用`r.ParseMultipartForm`,里面的参数表示`maxMemory`,调用`ParseMultipartForm`之后,上传的文件存储在`maxMemory`大小的内存里面,如果文件大小超过了`maxMemory`,那么剩下的部分将存储在系统的临时文件中。我们可以通过`r.FormFile`获取上面的文件句柄,然后实例中使用了`io.Copy`来存储文件。 >获取其他非文件字段信息的时候就不需要调用`r.ParseForm`,因为在需要的时候Go自动会去调用。而且`ParseMultipartForm`调用一次之后,后面再次调用不会再有效果。 @@ -67,13 +70,14 @@ 3. 使用`r.FormFile`获取文件句柄,然后对文件进行存储等处理。 文件handler是multipart.FileHeader,里面存储了如下结构信息 +```Go type FileHeader struct { Filename string Header textproto.MIMEHeader // contains filtered or unexported fields } - +``` 我们通过上面的实例代码打印出来上传文件的信息如下 ![](images/4.5.upload2.png?raw=true) @@ -83,6 +87,7 @@ ## 客户端上传文件 我们上面的例子演示了如何通过表单上传文件,然后在服务器端处理文件,其实Go支持模拟客户端表单功能支持文件上传,详细用法请看如下示例: +```Go package main @@ -145,7 +150,7 @@ postFile(filename, target_url) } - +``` 上面的例子详细展示了客户端如何向服务器上传一个文件的例子,客户端通过multipart.Write把文件的文本流写入一个缓存中,然后调用http的Post方法把缓存传到服务器。 >如果你还有其他普通字段例如username之类的需要同时写入,那么可以调用multipart的WriteField方法写很多其他类似的字段。 diff --git a/zh/05.1.md b/zh/05.1.md index 44a8207d..f98163f8 100644 --- a/zh/05.1.md +++ b/zh/05.1.md @@ -5,6 +5,7 @@ Go与PHP不同的地方是Go官方没有提供数据库驱动,而是为开发 这个存在于database/sql的函数是用来注册数据库驱动的,当第三方开发者开发数据库驱动时,都会实现init函数,在init里面会调用这个`Register(name string, driver driver.Driver)`完成本驱动的注册。 我们来看一下mymysql、sqlite3的驱动里面都是怎么调用的: +```Go //https://github.com/mattn/go-sqlite3驱动 func init() { @@ -18,13 +19,14 @@ Go与PHP不同的地方是Go官方没有提供数据库驱动,而是为开发 Register("SET NAMES utf8") sql.Register("mymysql", &d) } - +``` 我们看到第三方数据库驱动都是通过调用这个函数来注册自己的数据库驱动名称以及相应的driver实现。在database/sql内部通过一个map来存储用户定义的相应驱动。 +```Go var drivers = make(map[string]driver.Driver) drivers[name] = driver - +``` 因此通过database/sql的注册函数可以同时注册多个数据库驱动,只要不重复。 >在我们使用database/sql接口和第三方库的时候经常看到如下: @@ -40,31 +42,34 @@ Go与PHP不同的地方是Go官方没有提供数据库驱动,而是为开发 ## driver.Driver Driver是一个数据库驱动的接口,他定义了一个method: Open(name string),这个方法返回一个数据库的Conn接口。 +```Go type Driver interface { Open(name string) (Conn, error) } - +``` 返回的Conn只能用来进行一次goroutine的操作,也就是说不能把这个Conn应用于Go的多个goroutine里面。如下代码会出现错误 +```Go ... go goroutineA (Conn) //执行查询操作 go goroutineB (Conn) //执行插入操作 ... - +``` 上面这样的代码可能会使Go不知道某个操作究竟是由哪个goroutine发起的,从而导致数据混乱,比如可能会把goroutineA里面执行的查询操作的结果返回给goroutineB从而使B错误地把此结果当成自己执行的插入数据。 第三方驱动都会定义这个函数,它会解析name参数来获取相关数据库的连接信息,解析完成后,它将使用此信息来初始化一个Conn并返回它。 ## driver.Conn Conn是一个数据库连接的接口定义,他定义了一系列方法,这个Conn只能应用在一个goroutine里面,不能使用在多个goroutine里面,详情请参考上面的说明。 +```Go type Conn interface { Prepare(query string) (Stmt, error) Close() error Begin() (Tx, error) } - +``` Prepare函数返回与当前连接相关的执行Sql语句的准备状态,可以进行查询、删除等操作。 Close函数关闭当前的连接,执行释放连接拥有的资源等清理工作。因为驱动实现了database/sql里面建议的conn pool,所以你不用再去实现缓存conn之类的,这样会容易引起问题。 @@ -73,6 +78,7 @@ Begin函数返回一个代表事务处理的Tx,通过它你可以进行查询, ## driver.Stmt Stmt是一种准备好的状态,和Conn相关联,而且只能应用于一个goroutine中,不能应用于多个goroutine。 +```Go type Stmt interface { Close() error @@ -80,7 +86,7 @@ Stmt是一种准备好的状态,和Conn相关联,而且只能应用于一个 Exec(args []Value) (Result, error) Query(args []Value) (Rows, error) } - +``` Close函数关闭当前的链接状态,但是如果当前正在执行query,query还是有效返回rows数据。 NumInput函数返回当前预留参数的个数,当返回>=0时数据库驱动就会智能检查调用者的参数。当数据库驱动包不知道预留参数的时候,返回-1。 @@ -92,44 +98,48 @@ Query函数执行Prepare准备好的sql,传入需要的参数执行select操 ## driver.Tx 事务处理一般就两个过程,递交或者回滚。数据库驱动里面也只需要实现这两个函数就可以 +```Go type Tx interface { Commit() error Rollback() error } - +``` 这两个函数一个用来递交一个事务,一个用来回滚事务。 ## driver.Execer 这是一个Conn可选择实现的接口 +```Go type Execer interface { Exec(query string, args []Value) (Result, error) } - +``` 如果这个接口没有定义,那么在调用DB.Exec,就会首先调用Prepare返回Stmt,然后执行Stmt的Exec,然后关闭Stmt。 ## driver.Result 这个是执行Update/Insert等操作返回的结果接口定义 +```Go type Result interface { LastInsertId() (int64, error) RowsAffected() (int64, error) } - +``` LastInsertId函数返回由数据库执行插入操作得到的自增ID号。 RowsAffected函数返回query操作影响的数据条目数。 ## driver.Rows Rows是执行查询返回的结果集接口定义 +```Go type Rows interface { Columns() []string Close() error Next(dest []Value) error } - +``` Columns函数返回查询数据库表的字段信息,这个返回的slice和sql查询的字段一一对应,而不是返回整个表的所有字段。 Close函数用来关闭Rows迭代器。 @@ -139,19 +149,22 @@ Next函数用来返回下一条数据,把数据赋值给dest。dest里面的 ## driver.RowsAffected RowsAffected其实就是一个int64的别名,但是他实现了Result接口,用来底层实现Result的表示方式 +```Go type RowsAffected int64 func (RowsAffected) LastInsertId() (int64, error) func (v RowsAffected) RowsAffected() (int64, error) - +``` ## driver.Value Value其实就是一个空接口,他可以容纳任何的数据 +```Go type Value interface{} - +``` drive的Value是驱动必须能够操作的Value,Value要么是nil,要么是下面的任意一种 +```Go int64 float64 @@ -159,14 +172,15 @@ drive的Value是驱动必须能够操作的Value,Value要么是nil,要么是 []byte string [*]除了Rows.Next返回的不能是string. time.Time - +``` ## driver.ValueConverter ValueConverter接口定义了如何把一个普通的值转化成driver.Value的接口 +```Go type ValueConverter interface { ConvertValue(v interface{}) (Value, error) } - +``` 在开发的数据库驱动包里面实现这个接口的函数在很多地方会使用到,这个ValueConverter有很多好处: - 转化driver.value到数据库表相应的字段,例如int64的数据如何转化成数据库表uint16字段 @@ -175,17 +189,19 @@ ValueConverter接口定义了如何把一个普通的值转化成driver.Value的 ## driver.Valuer Valuer接口定义了返回一个driver.Value的方式 +```Go type Valuer interface { Value() (Value, error) } - +``` 很多类型都实现了这个Value方法,用来自身与driver.Value的转化。 通过上面的讲解,你应该对于驱动的开发有了一个基本的了解,一个驱动只要实现了这些接口就能完成增删查改等基本操作了,剩下的就是与相应的数据库进行数据交互等细节问题了,在此不再赘述。 ## database/sql database/sql在database/sql/driver提供的接口基础上定义了一些更高阶的方法,用以简化数据库操作,同时内部还建议性地实现一个conn pool。 +```Go type DB struct { driver driver.Driver @@ -194,7 +210,7 @@ database/sql在database/sql/driver提供的接口基础上定义了一些更高 freeConn []driver.Conn closed bool } - +``` 我们可以看到Open函数返回的是DB对象,里面有一个freeConn,它就是那个简易的连接池。它的实现相当简单或者说简陋,就是当执行Db.prepare的时候会`defer db.putConn(ci, err)`,也就是把这个连接放入连接池,每次调用conn的时候会先判断freeConn的长度是否大于0,大于0说明有可以复用的conn,直接拿出来用就是了,如果不大于0,则创建一个conn,然后再返回之。 diff --git a/zh/05.2.md b/zh/05.2.md index cbf68ca1..bc1aaf19 100644 --- a/zh/05.2.md +++ b/zh/05.2.md @@ -16,6 +16,7 @@ Go中支持MySQL的驱动目前比较多,有如下几种,有些是支持data ## 示例代码 接下来的几个小节里面我们都将采用同一个数据库表结构:数据库test,用户表userinfo,关联用户信息表userdetail。 +```sql CREATE TABLE `userinfo` ( `uid` INT(10) NOT NULL AUTO_INCREMENT, @@ -23,7 +24,7 @@ Go中支持MySQL的驱动目前比较多,有如下几种,有些是支持data `departname` VARCHAR(64) NULL DEFAULT NULL, `created` DATE NULL DEFAULT NULL, PRIMARY KEY (`uid`) - ) + ); CREATE TABLE `userdetail` ( `uid` INT(10) NOT NULL DEFAULT '0', @@ -31,8 +32,9 @@ Go中支持MySQL的驱动目前比较多,有如下几种,有些是支持data `profile` TEXT NULL, PRIMARY KEY (`uid`) ) - +``` 如下示例将示范如何使用database/sql接口对数据库表进行增删改查操作 +```Go package main @@ -108,7 +110,8 @@ Go中支持MySQL的驱动目前比较多,有如下几种,有些是支持data panic(err) } } - + +``` 通过上面的代码我们可以看出,Go操作Mysql数据库是很方便的。 diff --git a/zh/05.3.md b/zh/05.3.md index 4457a986..da414b0d 100644 --- a/zh/05.3.md +++ b/zh/05.3.md @@ -13,6 +13,7 @@ Go支持sqlite的驱动也比较多,但是好多都是不支持database/sql接 ## 实例代码 示例的数据库表结构如下所示,相应的建表SQL: +```sql CREATE TABLE `userinfo` ( `uid` INTEGER PRIMARY KEY AUTOINCREMENT, @@ -27,8 +28,9 @@ Go支持sqlite的驱动也比较多,但是好多都是不支持database/sql接 `profile` TEXT NULL, PRIMARY KEY (`uid`) ); - +``` 看下面Go程序是如何操作数据库表数据:增删改查 +```Go package main @@ -104,7 +106,7 @@ Go支持sqlite的驱动也比较多,但是好多都是不支持database/sql接 panic(err) } } - +``` 我们可以看到上面的代码和MySQL例子里面的代码几乎是一模一样的,唯一改变的就是导入的驱动改变了,然后调用`sql.Open`是采用了SQLite的方式打开。 diff --git a/zh/05.4.md b/zh/05.4.md index 02a4b2fa..7b75671a 100644 --- a/zh/05.4.md +++ b/zh/05.4.md @@ -17,6 +17,7 @@ Go实现的支持PostgreSQL的驱动也很多,因为国外很多人在开发 ## 实例代码 数据库建表语句: +```sql CREATE TABLE userinfo ( @@ -36,14 +37,17 @@ Go实现的支持PostgreSQL的驱动也很多,因为国外很多人在开发 ) WITH(OIDS=FALSE); +``` 看下面这个Go如何操作数据库表数据:增删改查 -package main +```Go + + package main import ( "database/sql" "fmt" - _ "https://github.com/lib/pq" + _ "github.com/lib/pq" ) func main() { @@ -58,10 +62,15 @@ package main checkErr(err) //pg不支持这个函数,因为他没有类似MySQL的自增ID - id, err := res.LastInsertId() - checkErr(err) + // id, err := res.LastInsertId() + // checkErr(err) + // fmt.Println(id) + + var lastInsertId int + err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId) + checkErr(err) + fmt.Println("最后插入id =", lastInsertId) - fmt.Println(id) //更新数据 stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2") @@ -113,6 +122,7 @@ package main panic(err) } } +``` 从上面的代码我们可以看到,PostgreSQL是通过`$1`,`$2`这种方式来指定要传递的参数,而不是MySQL中的`?`,另外在sql.Open中的dsn信息的格式也与MySQL的驱动中的dsn格式不一样,所以在使用时请注意它们的差异。 diff --git a/zh/05.5.md b/zh/05.5.md index e923db76..1e6ef7c9 100644 --- a/zh/05.5.md +++ b/zh/05.5.md @@ -1,58 +1,90 @@ -# 5.5 使用beedb库进行ORM开发 -beedb是我开发的一个Go进行ORM操作的库,它采用了Go style方式对数据库进行操作,实现了struct到数据表记录的映射。beedb是一个十分轻量级的Go ORM框架,开发这个库的本意降低复杂的ORM学习曲线,尽可能在ORM的运行效率和功能之间寻求一个平衡,beedb是目前开源的Go ORM框架中实现比较完整的一个库,而且运行效率相当不错,功能也基本能满足需求。但是目前还不支持关系关联,这个是接下来版本升级的重点。 +# 5.5 使用beedb/beego orm(暂未完成,请参考beego.me文档)库进行ORM开发 +beedb/beego orm是我开发的一个Go进行ORM操作的库,它采用了Go style方式对数据库进行操作,实现了struct到数据表记录的映射。beedb是一个十分轻量级的Go ORM框架,开发这个库的本意降低复杂的ORM学习曲线,尽可能在ORM的运行效率和功能之间寻求一个平衡,beedb是目前开源的Go ORM框架中实现比较完整的一个库,而且运行效率相当不错,功能也基本能满足需求。但是目前还不支持关系关联,这个是接下来版本升级的重点。 -beedb是支持database/sql标准接口的ORM库,所以理论上来说,只要数据库驱动支持database/sql接口就可以无缝的接入beedb。目前我测试过的驱动包括下面几个: +beedb/beego orm是支持database/sql标准接口的ORM库,所以理论上来说,只要数据库驱动支持database/sql接口就可以无缝的接入beedb。目前我测试过的驱动包括下面几个: -Mysql:github.com/ziutek/mymysql/godrv[*] -Mysql:code.google.com/p/go-mysql-driver[*] +Mysql: [github/go-mysql-driver/mysql](https://github.com/go-sql-driver/mysql) -PostgreSQL:github.com/bmizerany/pq[*] +PostgreSQL: [github.com/bmizerany/pq](https://github.com/lib/pq) -SQLite:github.com/mattn/go-sqlite3[*] +SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) -MS ADODB: github.com/mattn/go-adodb[*] +Mysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql) + +MsSql: [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) + +MS ADODB: [github.com/mattn/go-adodb](https://github.com/mattn/go-adodb) + +Oracle: [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) + +ODBC: [bitbucket.org/miquella/mgodbc](https://bitbucket.org/miquella/mgodbc) -ODBC: bitbucket.org/miquella/mgodbc[*] ## 安装 +beego orm支持go get方式安装,是完全按照Go Style的方式来实现的。 + + go get github.com/astaxie/beego + beedb支持go get方式安装,是完全按照Go Style的方式来实现的。 - go get github.com/astaxie/beedb + go get github.com/astaxie/beedb ## 如何初始化 -首先你需要import相应的数据库驱动包、database/sql标准接口包以及beedb包,如下所示: +首先你需要import相应的数据库驱动包、database/sql标准接口包以及beego orm包,如下所示: +```Go import ( "database/sql" - "github.com/astaxie/beedb" - _ "github.com/ziutek/mymysql/godrv" + "github.com/astaxie/beego/orm" + _ "github.com/go-sql-driver/mysql" ) +``` +导入必须的package之后,我们需要打开到数据库的链接,然后创建一个beego orm对象(以MySQL为例),如下所示 +beego orm: -导入必须的package之后,我们需要打开到数据库的链接,然后创建一个beedb对象(以MySQL为例),如下所示 +```Go + func init() { + // set default database + orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30) + + } + + func main() { + orm := orm.NewOrm() + } +``` + +beedb: + +```Go db, err := sql.Open("mymysql", "test/xiemengjun/123456") if err != nil { panic(err) } orm := beedb.New(db) - +``` beedb的New函数实际上应该有两个参数,第一个参数标准接口的db,第二个参数是使用的数据库引擎,如果你使用的数据库引擎是MySQL/Sqlite,那么第二个参数都可以省略。 如果你使用的数据库是SQLServer,那么初始化需要: +```Go orm = beedb.New(db, "mssql") - +``` 如果你使用了PostgreSQL,那么初始化需要: +```Go orm = beedb.New(db, "pg") - +``` 目前beedb支持打印调试,你可以通过如下的代码实现调试 +```Go beedb.OnDebug=true - +``` 接下来我们的例子采用前面的数据库表Userinfo,现在我们建立相应的struct +```Go type Userinfo struct { Uid int `PK` //如果表的主键不是id,那么需要加上pk注释,显式的说这个字段是主键 @@ -61,28 +93,32 @@ beedb的New函数实际上应该有两个参数,第一个参数标准接口的 Created time.Time } ->注意一点,beedb针对驼峰命名会自动帮你转化成下划线字段,例如你定义了Struct名字为`UserInfo`,那么转化成底层实现的时候是`user_info`,字段命名也遵循该规则。 +``` +>注意一点,beego orm针对驼峰命名会自动帮你转化成下划线字段,例如你定义了Struct名字为`UserInfo`,那么转化成底层实现的时候是`user_info`,字段命名也遵循该规则。 ## 插入数据 下面的代码演示了如何插入一条记录,可以看到我们操作的是struct对象,而不是原生的sql语句,最后通过调用Save接口将数据保存到数据库。 +```Go var saveone Userinfo saveone.Username = "Test Add User" saveone.Departname = "Test Add Departname" saveone.Created = time.Now() orm.Save(&saveone) - +``` 我们看到插入之后`saveone.Uid`就是插入成功之后的自增ID。Save接口会自动帮你存进去。 beedb接口提供了另外一种插入的方式,map数据插入。 +```Go add := make(map[string]interface{}) add["username"] = "astaxie" add["departname"] = "cloud develop" add["created"] = "2012-12-02" orm.SetTable("userinfo").Insert(add) - +``` 插入多条数据 +```Go addslice := make([]map[string]interface{}, 0) add:=make(map[string]interface{}) @@ -95,25 +131,27 @@ beedb接口提供了另外一种插入的方式,map数据插入。 add2["created"] = "2012-12-02" addslice =append(addslice, add, add2) orm.SetTable("userinfo").InsertBatch(addslice) - +``` 上面的操作方式有点类似链式查询,熟悉jquery的同学应该会觉得很亲切,每次调用的method都会返回原orm对象,以便可以继续调用该对象上的其他method。 上面我们调用的SetTable函数是显式的告诉ORM,我要执行的这个map对应的数据库表是`userinfo`。 ## 更新数据 继续上面的例子来演示更新操作,现在saveone的主键已经有值了,此时调用save接口,beedb内部会自动调用update以进行数据的更新而非插入操作。 +```Go saveone.Username = "Update Username" saveone.Departname = "Update Departname" saveone.Created = time.Now() orm.Save(&saveone) //现在saveone有了主键值,就执行更新操作 - +``` 更新数据也支持直接使用map操作 +```Go t := make(map[string]interface{}) t["username"] = "astaxie" orm.SetTable("userinfo").SetPK("uid").Where(2).Update(t) - +``` 这里我们调用了几个beedb的函数 SetPK:显式的告诉ORM,数据库表`userinfo`的主键是`uid`。 @@ -125,46 +163,55 @@ Updata函数接收map类型的数据,执行更新数据。 beedb的查询接口比较灵活,具体使用请看下面的例子 例子1,根据主键获取数据: +```Go var user Userinfo //Where接受两个参数,支持整形参数 orm.Where("uid=?", 27).Find(&user) - +``` 例子2: +```Go var user2 Userinfo orm.Where(3).Find(&user2) // 这是上面版本的缩写版,可以省略主键 - +``` 例子3,不是主键类型的的条件: +```Go var user3 Userinfo //Where接受两个参数,支持字符型的参数 orm.Where("name = ?", "john").Find(&user3) + +``` 例子4,更加复杂的条件: +```Go var user4 Userinfo //Where支持三个参数 orm.Where("name = ? and age < ?", "john", 88).Find(&user4) - +``` 可以通过如下接口获取多条数据,请看示例 例子1,根据条件id>3,获取20位置开始的10条数据的数据 +```Go var allusers []Userinfo err := orm.Where("id > ?", "3").Limit(10,20).FindAll(&allusers) - +``` 例子2,省略limit第二个参数,默认从0开始,获取10条数据 +```Go var tenusers []Userinfo err := orm.Where("id > ?", "3").Limit(10).FindAll(&tenusers) - +``` 例子3,获取全部数据 +```Go var everyone []Userinfo err := orm.OrderBy("uid desc,username asc").FindAll(&everyone) - +``` 上面这些里面里面我们看到一个函数Limit,他是用来控制查询结构条数的。 Limit:支持两个参数,第一个参数表示查询的条数,第二个参数表示读取数据的起始位置,默认为0。 @@ -172,9 +219,10 @@ Limit:支持两个参数,第一个参数表示查询的条数,第二个参 OrderBy:这个函数用来进行查询排序,参数是需要排序的条件。 上面这些例子都是将获取的的数据直接映射成struct对象,如果我们只是想获取一些数据到map,以下方式可以实现: +```Go a, _ := orm.SetTable("userinfo").SetPK("uid").Where(2).Select("uid,username").FindMap() - +``` 上面和这个例子里面又出现了一个新的接口函数Select,这个函数用来指定需要查询多少个字段。默认为全部字段`*`。 FindMap()函数返回的是`[]map[string][]byte`类型,所以你需要自己作类型转换。 @@ -183,25 +231,29 @@ FindMap()函数返回的是`[]map[string][]byte`类型,所以你需要自己 beedb提供了丰富的删除数据接口,请看下面的例子 例子1,删除单条数据 +```Go //saveone就是上面示例中的那个saveone orm.Delete(&saveone) - +``` 例子2,删除多条数据 +```Go //alluser就是上面定义的获取多条数据的slice orm.DeleteAll(&alluser) - +``` 例子3,根据sql删除数据 +```Go orm.SetTable("userinfo").Where("uid>?", 3).DeleteRow() - +``` ## 关联查询 目前beedb还不支持struct的关联关系,但是有些应用却需要用到连接查询,所以现在beedb提供了一个简陋的实现方案: +```Go a, _ := orm.SetTable("userinfo").Join("LEFT", "userdeatail", "userinfo.uid=userdeatail.uid").Where("userinfo.uid=?", 1).Select("userinfo.uid,userinfo.username,userdeatail.profile").FindMap() - +``` 上面代码中我们看到了一个新的接口Join函数,这个函数带有三个参数 - 第一个参数可以是:INNER, LEFT, OUTER, CROSS等 @@ -211,9 +263,10 @@ beedb提供了丰富的删除数据接口,请看下面的例子 ## Group By和Having 针对有些应用需要用到group by和having的功能,beedb也提供了一个简陋的实现 +```Go a, _ := orm.SetTable("userinfo").GroupBy("username").Having("username='astaxie'").FindMap() - +``` 上面的代码中出现了两个新接口函数 GroupBy:用来指定进行groupby的字段 @@ -225,7 +278,7 @@ Having:用来指定having执行的时候的条件 - 实现interface设计,类似databse/sql/driver的设计,设计beedb的接口,然后去实现相应数据库的CRUD操作 - 实现关联数据库设计,支持一对一,一对多,多对多的实现,示例代码如下: - +```Go type Profile struct{ Nickname string @@ -239,7 +292,7 @@ Having:用来指定having执行的时候的条件 Created time.Time Profile `HasOne` } - +``` - 自动建库建表建索引 - 实现连接池的实现,采用goroutine diff --git a/zh/05.6.md b/zh/05.6.md index 3fa765c2..6b8ef977 100644 --- a/zh/05.6.md +++ b/zh/05.6.md @@ -9,6 +9,7 @@ redis是一个key-value存储系统。和Memcached类似,它支持存储的val 目前应用redis最广泛的应该是新浪微博平台,其次还有Facebook收购的图片社交网站instagram。以及其他一些有名的[互联网企业](http://redis.io/topics/whos-using-redis) Go目前支持redis的驱动有如下 +- https://github.com/garyburd/redigo (推荐) - https://github.com/alphazero/Go-Redis - http://code.google.com/p/tideland-rdc/ - https://github.com/simonz05/godis @@ -19,6 +20,7 @@ Go目前支持redis的驱动有如下 https://github.com/astaxie/goredis 接下来的以我自己fork的这个redis驱动为例来演示如何进行数据的操作 +```Go package main @@ -50,6 +52,7 @@ https://github.com/astaxie/goredis client.Del("l") } +``` 我们可以看到操作redis非常的方便,而且我实际项目中应用下来性能也很高。client的命令和redis的命令基本保持一致。所以和原生态操作redis非常类似。 ## mongoDB @@ -65,6 +68,7 @@ MongoDB是一个高性能,开源,无模式的文档型数据库,是一个 目前Go支持mongoDB最好的驱动就是[mgo](http://labix.org/mgo),这个驱动目前最有可能成为官方的pkg。 下面我将演示如何通过Go来操作mongoDB: +```Go package main @@ -104,6 +108,7 @@ MongoDB是一个高性能,开源,无模式的文档型数据库,是一个 fmt.Println("Phone:", result.Phone) } +``` 我们可以看出来mgo的操作方式和beedb的操作方式几乎类似,都是基于struct的操作方式,这个就是Go Style。 diff --git a/zh/06.1.md b/zh/06.1.md index ba2b1241..1a4c260f 100644 --- a/zh/06.1.md +++ b/zh/06.1.md @@ -35,10 +35,12 @@ cookie是有时间限制的,根据生命期不同分成两种:会话cookie ### Go设置cookie Go语言中通过net/http包中的SetCookie来设置: +```Go http.SetCookie(w ResponseWriter, cookie *Cookie) - +``` w表示需要写入的response,cookie是一个struct,让我们来看一下cookie对象是怎么样的 +```Go type Cookie struct { Name string @@ -58,26 +60,30 @@ w表示需要写入的response,cookie是一个struct,让我们来看一下co Unparsed []string // Raw text of unparsed attribute-value pairs } +``` 我们来看一个例子,如何设置cookie +```Go expiration := time.Now() expiration = expiration.AddDate(1, 0, 0) cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration} http.SetCookie(w, &cookie) - +```    ### Go读取cookie 上面的例子演示了如何设置cookie数据,我们这里来演示一下如何读取cookie +```Go cookie, _ := r.Cookie("username") fmt.Fprint(w, cookie) - +``` 还有另外一种读取方式 +```Go for _, cookie := range r.Cookies() { fmt.Fprint(w, cookie.Name) } - +``` 可以看到通过request获取cookie非常方便。 ## session diff --git a/zh/06.2.md b/zh/06.2.md index d1ed7a42..282d645c 100644 --- a/zh/06.2.md +++ b/zh/06.2.md @@ -32,6 +32,7 @@ session的基本原理是由服务器为每个会话维护一份信息数据, ### Session管理器 定义一个全局的session管理器 +```Go type Manager struct { cookieName string //private cookiename @@ -48,15 +49,18 @@ session的基本原理是由服务器为每个会话维护一份信息数据, return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil } +``` Go实现整个的流程应该也是这样的,在main包中创建一个全局的session管理器 +```Go var globalSessions *session.Manager //然后在init函数中初始化 func init() { globalSessions, _ = NewManager("memory","gosessionid",3600) } - +``` 我们知道session是保存在服务器端的数据,它可以以任何的方式存储,比如存储在内存、数据库或者文件中。因此我们抽象出一个Provider接口,用以表征session管理器底层存储结构。 +```Go type Provider interface { SessionInit(sid string) (Session, error) @@ -64,13 +68,14 @@ Go实现整个的流程应该也是这样的,在main包中创建一个全局 SessionDestroy(sid string) error SessionGC(maxLifeTime int64) } - +``` - SessionInit函数实现Session的初始化,操作成功则返回此新的Session变量 - SessionRead函数返回sid所代表的Session变量,如果不存在,那么将以sid为参数调用SessionInit函数创建并返回一个新的Session变量 - SessionDestroy函数用来销毁sid对应的Session变量 - SessionGC根据maxLifeTime来删除过期的数据 那么Session接口需要实现什么样的功能呢?有过Web开发经验的读者知道,对Session的处理基本就 设置值、读取值、删除值以及获取当前sessionID这四个操作,所以我们的Session接口也就实现这四个操作。 +```Go type Session interface { Set(key, value interface{}) error //set session value @@ -78,9 +83,11 @@ Go实现整个的流程应该也是这样的,在main包中创建一个全局 Delete(key interface{}) error //delete session value SessionID() string //back current sessionID } - +``` >以上设计思路来源于database/sql/driver,先定义好接口,然后具体的存储session的结构实现相应的接口并注册后,相应功能这样就可以使用了,以下是用来随需注册存储session的结构的Register函数的实现。 +```Go + var provides = make(map[string]Provider) // Register makes a session provide available by the provided name. @@ -95,11 +102,13 @@ Go实现整个的流程应该也是这样的,在main包中创建一个全局 } provides[name] = provider } - +``` ### 全局唯一的Session ID Session ID是用来识别访问Web应用的每一个用户,因此必须保证它是全局唯一的(GUID),下面代码展示了如何满足这一需求: +```Go + func (manager *Manager) sessionId() string { b := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, b); err != nil { @@ -107,9 +116,10 @@ Session ID是用来识别访问Web应用的每一个用户,因此必须保证 } return base64.URLEncoding.EncodeToString(b) } - +``` ### session创建 我们需要为每个来访用户分配或获取与他相关连的Session,以便后面根据Session信息来验证操作。SessionStart这个函数就是用来检测是否已经有某个Session与当前来访用户发生了关联,如果没有则创建之。 +```Go func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) { manager.lock.Lock() @@ -126,8 +136,9 @@ Session ID是用来识别访问Web应用的每一个用户,因此必须保证 } return } - +``` 我们用前面login操作来演示session的运用: +```Go func login(w http.ResponseWriter, r *http.Request) { sess := globalSessions.SessionStart(w, r) @@ -141,11 +152,12 @@ Session ID是用来识别访问Web应用的每一个用户,因此必须保证 http.Redirect(w, r, "/", 302) } } - +``` ### 操作值:设置、读取和删除 SessionStart函数返回的是一个满足Session接口的变量,那么我们该如何用他来对session数据进行操作呢? 上面的例子中的代码`session.Get("uid")`已经展示了基本的读取数据的操作,现在我们再来看一下详细的操作: +```Go func count(w http.ResponseWriter, r *http.Request) { sess := globalSessions.SessionStart(w, r) @@ -166,13 +178,14 @@ SessionStart函数返回的是一个满足Session接口的变量,那么我们 w.Header().Set("Content-Type", "text/html") t.Execute(w, sess.Get("countnum")) } - +``` 通过上面的例子可以看到,Session的操作和操作key/value数据库类似:Set、Get、Delete等操作 因为Session有过期的概念,所以我们定义了GC操作,当访问过期时间满足GC的触发条件后将会引起GC,但是当我们进行了任意一个session操作,都会对Session实体进行更新,都会触发对最后访问时间的修改,这样当GC的时候就不会误删除还在使用的Session实体。 ### session重置 我们知道,Web应用中有用户退出这个操作,那么当用户退出应用的时候,我们需要对该用户的session数据进行销毁操作,上面的代码已经演示了如何使用session重置操作,下面这个函数就是实现了这个功能: +```Go //Destroy sessionid func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){ @@ -189,9 +202,10 @@ SessionStart函数返回的是一个满足Session接口的变量,那么我们 } } - +``` ### session销毁 我们来看一下Session管理器如何来管理销毁,只要我们在Main启动的时候启动: +```Go func init() { go globalSessions.GC() @@ -203,7 +217,7 @@ SessionStart函数返回的是一个满足Session接口的变量,那么我们 manager.provider.SessionGC(manager.maxlifetime) time.AfterFunc(time.Duration(manager.maxlifetime), func() { manager.GC() }) } - +``` 我们可以看到GC充分利用了time包中的定时器功能,当超时`maxLifeTime`之后调用GC函数,这样就可以保证`maxLifeTime`时间内的session都是可用的,类似的方案也可以用于统计在线用户数之类的。 ## 总结 diff --git a/zh/06.3.md b/zh/06.3.md index 5ffa3d8d..86a0aada 100644 --- a/zh/06.3.md +++ b/zh/06.3.md @@ -1,5 +1,6 @@ # 6.3 session存储 上一节我们介绍了Session管理器的实现原理,定义了存储session的接口,这小节我们将示例一个基于内存的session存储接口的实现,其他的存储方式,读者可以自行参考示例来实现,内存的实现请看下面的例子代码 +```Go package memory @@ -112,15 +113,17 @@ pder.sessions = make(map[string]*list.Element, 0) session.Register("memory", pder) } - +``` 上面这个代码实现了一个内存存储的session机制。通过init函数注册到session管理器中。这样就可以方便的调用了。我们如何来调用该引擎呢?请看下面的代码 +```Go import ( "github.com/astaxie/session" _ "github.com/astaxie/session/providers/memory" ) - +``` 当import的时候已经执行了memory函数里面的init函数,这样就已经注册到session管理器中,我们就可以使用了,通过如下方式就可以初始化一个session管理器: +```Go var globalSessions *session.Manager @@ -130,7 +133,7 @@ go globalSessions.GC() } - +``` ## links * [目录]() * 上一节: [Go如何使用session](<06.2.md>) diff --git a/zh/06.4.md b/zh/06.4.md index 6216c7a6..db3b5257 100644 --- a/zh/06.4.md +++ b/zh/06.4.md @@ -4,6 +4,7 @@ session劫持是一种广泛存在的比较严重的安全威胁,在session技 本节将通过一个实例来演示会话劫持,希望通过这个实例,能让读者更好地理解session的本质。 ## session劫持过程 我们写了如下的代码来展示一个count计数器: +```Go func count(w http.ResponseWriter, r *http.Request) { sess := globalSessions.SessionStart(w, r) @@ -18,11 +19,12 @@ session劫持是一种广泛存在的比较严重的安全威胁,在session技 t.Execute(w, sess.Get("countnum")) } - +``` count.gtpl的代码如下所示: +```Go Hi. Now count:{{.}} - +``` 然后我们在浏览器里面刷新可以看到如下内容: ![](images/6.4.hijack.png?raw=true) @@ -56,6 +58,7 @@ count.gtpl的代码如下所示: 其中一个解决方案就是sessionID的值只允许cookie设置,而不是通过URL重置方式设置,同时设置cookie的httponly为true,这个属性是设置是否可通过客户端脚本访问这个设置的cookie,第一这个可以防止这个cookie被XSS读取从而引起session劫持,第二cookie设置不会像URL重置方式那么容易获取sessionID。 第二步就是在每个请求里面加上token,实现类似前面章节里面讲的防止form重复递交类似的功能,我们在每个请求里面加上一个隐藏的token,然后每次验证这个token,从而保证用户的请求都是唯一性。 +```Go h := md5.New() salt:="astaxie%^7&8888" @@ -66,9 +69,10 @@ count.gtpl的代码如下所示: } sess.Set("token",token) - +``` ### 间隔生成新的SID 还有一个解决方案就是,我们给session额外设置一个创建时间的值,一旦过了一定的时间,我们销毁这个sessionID,重新生成新的session,这样可以一定程度上防止session劫持的问题。 +```Go createtime := sess.Get("createtime") if createtime == nil { @@ -77,7 +81,7 @@ count.gtpl的代码如下所示: globalSessions.SessionDestroy(w, r) sess = globalSessions.SessionStart(w, r) } - +``` session启动后,我们设置了一个值,用于记录生成sessionID的时间。通过判断每次请求是否过期(这里设置了60秒)定期生成新的ID,这样使得攻击者获取有效sessionID的机会大大降低。 上面两个手段的组合可以在实践中消除session劫持的风险,一方面, 由于sessionID频繁改变,使攻击者难有机会获取有效的sessionID;另一方面,因为sessionID只能在cookie中传递,然后设置了httponly,所以基于URL攻击的可能性为零,同时被XSS获取sessionID也不可能。最后,由于我们还设置了MaxAge=0,这样就相当于session cookie不会留在浏览器的历史记录里面。 diff --git a/zh/07.1.md b/zh/07.1.md index bc321eb1..1731f578 100644 --- a/zh/07.1.md +++ b/zh/07.1.md @@ -4,6 +4,7 @@ XML作为一种数据交换和信息传递的格式已经十分普及。而随 这个小节不会涉及XML规范相关的内容(如需了解相关知识请参考其他文献),而是介绍如何用Go语言来编解码XML文件相关的知识。 假如你是一名运维人员,你为你所管理的所有服务器生成了如下内容的xml的配置文件: +```xml @@ -16,17 +17,19 @@ XML作为一种数据交换和信息传递的格式已经十分普及。而随 127.0.0.2 - +``` 上面的XML文档描述了两个服务器的信息,包含了服务器名和服务器的IP信息,接下来的Go例子以此XML描述的信息进行操作。 ## 解析XML 如何解析如上这个XML文件呢? 我们可以通过xml包的`Unmarshal`函数来达到我们的目的 +```Go func Unmarshal(data []byte, v interface{}) error - +``` data接收的是XML数据流,v是需要输出的结构,定义为interface,也就是可以把XML转换为任意的格式。我们这里主要介绍struct的转换,因为struct和XML都有类似树结构的特征。 示例代码如下: +```Go package main @@ -72,8 +75,9 @@ data接收的是XML数据流,v是需要输出的结构,定义为interface, fmt.Println(v) } - +``` XML本质上是一种树形的数据格式,而我们可以定义与之匹配的go 语言的 struct类型,然后通过xml.Unmarshal来将xml中的数据解析成对应的struct对象。如上例子输出如下数据 +```xml {{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}] @@ -86,11 +90,12 @@ XML本质上是一种树形的数据格式,而我们可以定义与之匹配 } - +``` 上面的例子中,将xml文件解析成对应的struct对象是通过`xml.Unmarshal`来完成的,这个过程是如何实现的?可以看到我们的struct定义后面多了一些类似于`xml:"serverName"`这样的内容,这个是struct的一个特性,它们被称为 struct tag,它们是用来辅助反射的。我们来看一下`Unmarshal`的定义: +```Go func Unmarshal(data []byte, v interface{}) error - +``` 我们看到函数定义了两个参数,第一个是XML数据流,第二个是存储的对应类型,目前支持struct、slice和string,XML包内部采用了反射来进行数据的映射,所以v里面的字段必须是导出的。`Unmarshal`解析的时候XML元素和字段怎么对应起来的呢?这是有一个优先级读取流程的,首先会读取struct tag,如果没有,那么就会对应字段名。必须注意一点的是解析的时候tag、字段名、XML元素都是大小写敏感的,所以必须一一对应字段。 Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的数据反射成对应的struct对象,关于反射如何利用struct tag的更多内容请参阅reflect中的相关内容。 @@ -99,6 +104,8 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的 - 如果struct的一个字段是string或者[]byte类型且它的tag含有`",innerxml"`,Unmarshal将会将此字段所对应的元素内所有内嵌的原始xml累加到此字段上,如上面例子Description定义。最后的输出是 +```xml + Shanghai_VPN 127.0.0.1 @@ -108,6 +115,8 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的 127.0.0.2 +``` + - 如果struct中有一个叫做XMLName,且类型为xml.Name字段,那么在解析的时候就会保存这个element的名字到该字段,如上面例子中的servers。 - 如果某个struct字段的tag定义中含有XML结构中element的名称,那么解析的时候就会把相应的element值赋值给该字段,如上servername和serverip定义。 - 如果某个struct字段的tag定义了中含有`",attr"`,那么解析的时候就会将该结构所对应的element的与字段同名的属性的值赋值给该字段,如上version定义。 @@ -122,13 +131,15 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的 ## 输出XML 假若我们不是要解析如上所示的XML文件,而是生成它,那么在go语言中又该如何实现呢? xml包中提供了`Marshal`和`MarshalIndent`两个函数,来满足我们的需求。这两个函数主要的区别是第二个函数会增加前缀和缩进,函数的定义如下所示: +```Go func Marshal(v interface{}) ([]byte, error) func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) - +``` 两个函数第一个参数是用来生成XML的结构定义类型数据,都是返回生成的XML数据流。 下面我们来看一下如何输出如上的XML: +```Go package main @@ -161,7 +172,10 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的 os.Stdout.Write(output) } + +``` 上面的代码输出如下信息: +```xml @@ -175,6 +189,7 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的 +``` 和我们之前定义的文件的格式一模一样,之所以会有`os.Stdout.Write([]byte(xml.Header))` 这句代码的出现,是因为`xml.MarshalIndent`或者`xml.Marshal`输出的信息都是不带XML头的,为了生成正确的xml文件,我们使用了xml包预定义的Header变量。 我们看到`Marshal`函数接收的参数v是interface{}类型的,即它可以接受任意类型的参数,那么xml包,根据什么规则来生成相应的XML文件呢? @@ -204,6 +219,7 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的 - tag中含有`"omitempty"`,如果该字段的值为空值那么该字段就不会被输出到XML,空值包括:false、0、nil指针或nil接口,任何长度为0的array, slice, map或者string - tag中含有`"a>b>c"`,那么就会循环输出三个元素a包含b,b包含c,例如如下代码就会输出 +```xml FirstName string `xml:"name>first"` LastName string `xml:"name>last"` @@ -212,7 +228,7 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的 Xie - +``` 上面我们介绍了如何使用Go语言的xml包来编/解码XML文件,重要的一点是对XML的所有操作都是通过struct tag来实现的,所以学会对struct tag的运用变得非常重要,在文章中我们简要的列举了如何定义tag。更多内容或tag定义请参看相应的官方资料。 ## links diff --git a/zh/07.2.md b/zh/07.2.md index c0244745..5f0cbbb3 100644 --- a/zh/07.2.md +++ b/zh/07.2.md @@ -2,18 +2,21 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言,以文字为基础,具有自我描述性且易于让人阅读。尽管JSON是Javascript的一个子集,但JSON是独立于语言的文本格式,并且采用了类似于C语言家族的一些习惯。JSON与XML最大的不同在于XML是一个完整的标记语言,而JSON不是。JSON由于比XML更小、更快,更易解析,以及浏览器的内建快速解析支持,使得其更适用于网络数据传输领域。目前我们看到很多的开放平台,基本上都是采用了JSON作为他们的数据交互的接口。既然JSON在Web开发中如此重要,那么Go语言对JSON支持的怎么样呢?Go语言的标准库已经非常好的支持了JSON,可以很容易的对JSON数据进行编、解码的工作。 前一小节的运维的例子用json来表示,结果描述如下: +```json {"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]} - +``` 本小节余下的内容将以此JSON数据为基础,来介绍go语言的json包对JSON数据的编、解码。 ## 解析JSON ### 解析到结构体 假如有了上面的JSON串,那么我们如何来解析这个JSON串呢?Go的JSON包中有如下函数 +```Go func Unmarshal(data []byte, v interface{}) error - +``` 通过这个函数我们就可以实现解析的目的,详细的解析例子请看如下代码: +```Go package main @@ -37,7 +40,7 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, json.Unmarshal([]byte(str), &s) fmt.Println(s) } - +``` 在上面的示例代码中,我们首先定义了与json数据对应的结构体,数组对应slice,字段名对应JSON里面的KEY,在解析的时候,如何将json数据与struct字段相匹配呢?例如JSON的key是`Foo`,那么怎么找对应的字段呢? - 首先查找tag含有`Foo`的可导出的struct字段(首字母大写) @@ -57,15 +60,18 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, - nil 代表 JSON null. 现在我们假设有如下的JSON数据 +```Go b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`) - +``` 如果在我们不知道他的结构的情况下,我们把他解析到interface{}里面 +```Go var f interface{} err := json.Unmarshal(b, &f) - +``` 这个时候f里面存储了一个map类型,他们的key是string,值存储在空的interface{}里 +```Go f = map[string]interface{}{ "Name": "Wednesday", @@ -75,12 +81,14 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, "Morticia", }, } - +``` 那么如何来访问这些数据呢?通过断言的方式: +```Go m := f.(map[string]interface{}) - +``` 通过断言之后,你就可以通过如下方式来访问里面的数据了 +```Go for k, v := range m { switch vv := v.(type) { @@ -99,9 +107,11 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, fmt.Println(k, "is of a type I don't know how to handle") } } +``` 通过上面的示例可以看到,通过interface{}与type assert的配合,我们就可以解析未知结构的JSON数了。 上面这个是官方提供的解决方案,其实很多时候我们通过类型断言,操作起来不是很方便,目前bitly公司开源了一个叫做`simplejson`的包,在处理未知结构体的JSON时相当方便,详细例子如下所示: +```Go js, err := NewJson([]byte(`{ "test": { @@ -118,14 +128,17 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, i, _ := js.Get("test").Get("int").Int() ms := js.Get("test").Get("string").MustString() +``` 可以看到,使用这个库操作JSON比起官方包来说,简单的多,详细的请参考如下地址:https://github.com/bitly/go-simplejson ## 生成JSON 我们开发很多应用的时候,最后都是要输出JSON数据串,那么如何来处理呢?JSON包里面通过`Marshal`函数来处理,函数定义如下: +```Go func Marshal(v interface{}) ([]byte, error) - +``` 假设我们还是需要生成上面的服务器列表信息,那么如何来处理呢?请看下面的例子: +```Go package main @@ -154,11 +167,14 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, fmt.Println(string(b)) } +``` 输出如下内容: +```json {"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]} - +``` 我们看到上面的输出字段名的首字母都是大写的,如果你想用小写的首字母怎么办呢?把结构体的字段名改成首字母小写的?JSON输出的时候必须注意,只有导出的字段才会被输出,如果修改字段名,那么就会发现什么都不会输出,所以必须通过struct tag定义来实现: +```Go type Server struct { ServerName string `json:"serverName"` @@ -168,7 +184,7 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, type Serverslice struct { Servers []Server `json:"servers"` } - +``` 通过修改上面的结构体定义,输出的JSON串就和我们最开始定义的JSON串保持一致了。 针对JSON的输出,我们在定义struct tag的时候需要注意的几点是: @@ -180,6 +196,7 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, 举例来说: +```Go type Server struct { // ID 不会导出到JSON中 @@ -202,11 +219,13 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, b, _ := json.Marshal(s) os.Stdout.Write(b) +``` 会输出以下内容: +```json {"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""} - +``` Marshal函数只有在转换成功的时候才会返回数据,在转换的过程中我们需要注意几点: diff --git a/zh/07.3.md b/zh/07.3.md index db55d633..74df4f1b 100644 --- a/zh/07.3.md +++ b/zh/07.3.md @@ -9,14 +9,17 @@ Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果 ## 通过正则判断是否匹配 `regexp`包中含有三个函数用来判断是否匹配,如果匹配返回true,否则返回false +```Go func Match(pattern string, b []byte) (matched bool, error error) func MatchReader(pattern string, r io.RuneReader) (matched bool, error error) func MatchString(pattern string, s string) (matched bool, error error) +``` 上面的三个函数实现了同一个功能,就是判断`pattern`是否和输入源匹配,匹配的话就返回true,如果解析正则出错则返回error。三个函数的输入源分别是byte slice、RuneReader和string。 如果要验证一个输入是不是IP地址,那么如何来判断呢?请看如下实现 +```Go func IsIP(ip string) (b bool) { if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m { @@ -24,8 +27,9 @@ Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果 } return true } - +``` 可以看到,`regexp`的pattern和我们平常使用的正则一模一样。再来看一个例子:当用户输入一个字符串,我们想知道是不是一次合法的输入: +```Go func main() { if len(os.Args) == 1 { @@ -37,13 +41,14 @@ Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果 fmt.Println("不是数字") } } - +``` 在上面的两个小例子中,我们采用了Match(Reader|String)来判断一些字符串是否符合我们的描述需求,它们使用起来非常方便。 ## 通过正则获取内容 Match模式只能用来对字符串的判断,而无法截取字符串的一部分、过滤字符串、或者提取出符合条件的一批字符串。如果想要满足这些需求,那就需要使用正则表达式的复杂模式。 我们经常需要一些爬虫程序,下面就以爬虫为例来说明如何使用正则来过滤或截取抓取到的数据: +```Go package main @@ -92,18 +97,21 @@ Match模式只能用来对字符串的判断,而无法截取字符串的一部 fmt.Println(strings.TrimSpace(src)) } +``` 从这个示例可以看出,使用复杂的正则首先是Compile,它会解析正则表达式是否合法,如果正确,那么就会返回一个Regexp,然后就可以利用返回的Regexp在任意的字符串上面执行需要的操作。 解析正则表达式的有如下几个方法: +```Go func Compile(expr string) (*Regexp, error) func CompilePOSIX(expr string) (*Regexp, error) func MustCompile(str string) *Regexp func MustCompilePOSIX(str string) *Regexp - +``` CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法,它使用最左最长方式搜索,而Compile是采用的则只采用最左方式搜索(例如[a-z]{2,4}这样一个正则表达式,应用于"aa09aaa88aaaa"这个文本串时,CompilePOSIX返回了aaaa,而Compile的返回的是aa)。前缀有Must的函数表示,在解析正则语法的时候,如果匹配模式串不满足正确的语法则直接panic,而不加Must的则只是返回错误。 在了解了如何新建一个Regexp之后,我们再来看一下这个struct提供了哪些方法来辅助我们操作字符串,首先我们来看下面这些用来搜索的函数: +```Go func (re *Regexp) Find(b []byte) []byte func (re *Regexp) FindAll(b []byte, n int) [][]byte @@ -123,8 +131,9 @@ CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法,它使用 func (re *Regexp) FindStringSubmatchIndex(s string) []int func (re *Regexp) FindSubmatch(b []byte) [][]byte func (re *Regexp) FindSubmatchIndex(b []byte) []int - +``` 上面这18个函数我们根据输入源(byte slice、string和io.RuneReader)不同还可以继续简化成如下几个,其他的只是输入源不一样,其他功能基本是一样的: +```Go func (re *Regexp) Find(b []byte) []byte func (re *Regexp) FindAll(b []byte, n int) [][]byte @@ -134,8 +143,9 @@ CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法,它使用 func (re *Regexp) FindIndex(b []byte) (loc []int) func (re *Regexp) FindSubmatch(b []byte) [][]byte func (re *Regexp) FindSubmatchIndex(b []byte) []int - +``` 对于这些函数的使用我们来看下面这个例子 +```Go package main @@ -189,14 +199,16 @@ CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法,它使用 submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1) fmt.Println(submatchallindex) } - +``` 前面介绍过匹配函数,Regexp也定义了三个函数,它们和同名的外部函数功能一模一样,其实外部函数就是调用了这Regexp的三个函数来实现的: +```Go func (re *Regexp) Match(b []byte) bool func (re *Regexp) MatchReader(r io.RuneReader) bool func (re *Regexp) MatchString(s string) bool - +``` 接下里让我们来了解替换函数是怎么操作的? +```Go func (re *Regexp) ReplaceAll(src, repl []byte) []byte func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte @@ -204,15 +216,17 @@ CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法,它使用 func (re *Regexp) ReplaceAllLiteralString(src, repl string) string func (re *Regexp) ReplaceAllString(src, repl string) string func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string - +``` 这些替换函数我们在上面的抓网页的例子有详细应用示例, 接下来我们看一下Expand的解释: +```Go func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte - +``` 那么这个Expand到底用来干嘛的呢?请看下面的例子: +```Go func main() { src := []byte(` @@ -227,7 +241,7 @@ CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法,它使用 } fmt.Println(string(res)) } - +``` 至此我们已经全部介绍完Go语言的`regexp`包,通过对它的主要函数介绍及演示,相信大家应该能够通过Go语言的正则包进行一些基本的正则的操作了。 diff --git a/zh/07.4.md b/zh/07.4.md index f90fdb81..48db045a 100644 --- a/zh/07.4.md +++ b/zh/07.4.md @@ -12,6 +12,7 @@ Web应用反馈给客户端的信息中的大部分内容是静态的,不变 ## Go模板使用 在Go语言中,我们使用`template`包来进行模板处理,使用类似`Parse`、`ParseFile`、`Execute`等方法从文件或者字符串加载模板,然后执行类似上面图片展示的模板的merge操作。请看下面的例子: +```Go func handler(w http.ResponseWriter, r *http.Request) { t := template.New("some template") //创建一个模板 @@ -19,7 +20,7 @@ Web应用反馈给客户端的信息中的大部分内容是静态的,不变 user := GetUser() //获取当前用户信息 t.Execute(w, user) //执行模板的merger操作 } - +``` 通过上面的例子我们可以看到Go语言的模板操作非常的简单方便,和其他语言的模板处理类似,都是先获取数据,然后渲染数据。 为了演示和测试代码的方便,我们在接下来的例子中采用如下格式的代码 @@ -33,6 +34,7 @@ Web应用反馈给客户端的信息中的大部分内容是静态的,不变 ### 字段操作 Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{.}}`表示当前的对象,这和Java或者C++中的this类似,如果要访问当前对象的字段通过`{{.FieldName}}`,但是需要注意一点:这个字段必须是导出的(字段首字母必须是大写的),否则在渲染的时候就会报错,请看下面的这个例子: +```Go package main @@ -51,8 +53,9 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{ p := Person{UserName: "Astaxie"} t.Execute(os.Stdout, p) } - +``` 上面的代码我们可以正确的输出`hello Astaxie`,但是如果我们稍微修改一下代码,在模板中含有了未导出的字段,那么就会报错 +```Go type Person struct { UserName string @@ -60,7 +63,7 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{ } t, _ = t.Parse("hello {{.UserName}}! {{.email}}") - +``` 上面的代码就会报错,因为我们调用了一个未导出的字段,但是如果我们调用了一个不存在的字段是不会报错的,而是输出为空。 如果模板中输出`{{.}}`,这个一般应用于字符串对象,默认会调用fmt包输出字符串的内容。 @@ -72,6 +75,7 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{ - {{with}}操作是指当前对象的值,类似上下文的概念 详细的使用请看下面的例子: +```Go package main @@ -109,9 +113,10 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{ Friends: []*Friend{&f1, &f2}} t.Execute(os.Stdout, p) } - +``` ### 条件处理 在Go模板里面如果需要进行条件判断,那么我们可以使用和Go语言的`if-else`语法类似的方式来处理,如果pipeline为空,那么if就认为是false,下面的例子展示了如何使用`if-else`语法: +```Go package main @@ -133,44 +138,54 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{ tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if部分 {{else}} else部分.{{end}}\n")) tIfElse.Execute(os.Stdout, nil) } - +``` 通过上面的演示代码我们知道`if-else`语法相当的简单,在使用过程中很容易集成到我们的模板代码中。 > 注意:if里面无法使用条件判断,例如.Mail=="astaxie@gmail.com",这样的判断是不正确的,if里面只能是bool值 ### pipelines Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的语法你是不是经常使用,过滤当前目录下面的文件,显示含有"beego"的数据,表达的意思就是前面的输出可以当做后面的输入,最后显示我们想要的数据,而Go语言模板最强大的一点就是支持pipe数据,在Go语言里面任何`{{}}`里面的都是pipelines数据,例如我们上面输出的email里面如果还有一些可能引起XSS注入的,那么我们如何来进行转化呢? +```Go {{. | html}} - + +``` 在email输出的地方我们可以采用如上方式可以把输出全部转化html的实体,上面的这种方式和我们平常写Unix的方式是不是一模一样,操作起来相当的简便,调用其他的函数也是类似的方式。 ### 模板变量 有时候,我们在模板使用过程中需要定义一些局部变量,我们可以在一些操作中申明局部变量,例如`with``range``if`过程中申明局部变量,这个变量的作用域是`{{end}}`之前,Go语言通过申明的局部变量格式如下所示: +```Go $variable := pipeline - +``` 详细的例子看下面的: +```Go {{with $x := "output" | printf "%q"}}{{$x}}{{end}} {{with $x := "output"}}{{printf "%q" $x}}{{end}} {{with $x := "output"}}{{$x | printf "%q"}}{{end}} + +``` ### 模板函数 模板在输出对象的字段值时,采用了`fmt`包把对象转化成了字符串。但是有时候我们的需求可能不是这样的,例如有时候我们为了防止垃圾邮件发送者通过采集网页的方式来发送给我们的邮箱信息,我们希望把`@`替换成`at`例如:`astaxie at beego.me`,如果要实现这样的功能,我们就需要自定义函数来做这个功能。 每一个模板函数都有一个唯一值的名字,然后与一个Go函数关联,通过如下的方式来关联 +```Go type FuncMap map[string]interface{} - +``` 例如,如果我们想要的email函数的模板函数名是`emailDeal`,它关联的Go函数名称是`EmailDealWith`,那么我们可以通过下面的方式来注册这个函数 +```Go t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) - +``` `EmailDealWith`这个函数的参数和返回值定义如下: +```Go func EmailDealWith(args …interface{}) string - +``` 我们来看下面的实现例子: +```Go package main @@ -230,8 +245,9 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的 t.Execute(os.Stdout, p) } - +``` 上面演示了如何自定义函数,其实,在模板包内部已经有内置的实现函数,下面代码截取自模板包里面 +```Go var builtins = FuncMap{ "and": and, @@ -248,9 +264,10 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的 "urlquery": URLQueryEscaper, } - +``` ## Must操作 模板包里面有一个函数`Must`,它的作用是检测模板是否正确,例如大括号是否匹配,注释是否正确的关闭,变量是否正确的书写。接下来我们演示一个例子,用Must来判断模板是否正确: +```Go package main @@ -272,23 +289,29 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的 template.Must(tErr.Parse(" some static text {{ .Name }")) } +``` 将输出如下内容 +``` + The first one parsed OK. The second one parsed OK. The next one ought to fail. panic: template: check parse error with Must:1: unexpected "}" in command - +``` ## 嵌套模板 我们平常开发Web应用的时候,经常会遇到一些模板有些部分是固定不变的,然后可以抽取出来作为一个独立的部分,例如一个博客的头部和尾部是不变的,而唯一改变的是中间的内容部分。所以我们可以定义成`header`、`content`、`footer`三个部分。Go语言中通过如下的语法来申明 +```Go {{define "子模板名称"}}内容{{end}} - +``` 通过如下方式来调用: +```Go {{template "子模板名称"}} - +``` 接下来我们演示如何使用嵌套模板,我们定义三个文件,`header.tmpl`、`content.tmpl`、`footer.tmpl`文件,里面的内容如下 +```html //header.tmpl {{define "header"}} @@ -315,8 +338,9 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的 {{end}} - +``` 演示代码如下: +```Go package main @@ -336,7 +360,7 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的 fmt.Println() s1.Execute(os.Stdout, nil) } - +``` 通过上面的例子我们可以看到通过`template.ParseFiles`把所有的嵌套模板全部解析到模板里面,其实每一个定义的{{define}}都是一个独立的模板,他们相互独立,是并行存在的关系,内部其实存储的是类似map的一种关系(key是模板的名称,value是模板的内容),然后我们通过`ExecuteTemplate`来执行相应的子模板内容,我们可以看到header、footer都是相对独立的,都能输出内容,content 中因为嵌套了header和footer的内容,就会同时输出三个的内容。但是当我们执行`s1.Execute`,没有任何的输出,因为在默认的情况下没有默认的子模板,所以不会输出任何的东西。 >同一个集合类的模板是互相知晓的,如果同一模板被多个集合使用,则它需要在多个集合中分别解析 diff --git a/zh/07.5.md b/zh/07.5.md index a059ab47..75bb7184 100644 --- a/zh/07.5.md +++ b/zh/07.5.md @@ -21,6 +21,7 @@ 下面是演示代码: +```Go package main @@ -39,6 +40,7 @@ os.RemoveAll("astaxie") } +``` ## 文件操作 @@ -80,6 +82,7 @@ 写入string信息到文件 写文件的示例代码 +```Go package main @@ -102,6 +105,7 @@ } } +``` ### 读文件 读文件函数: @@ -114,6 +118,7 @@ 从off开始读取数据到b中 读文件的示例代码: +```Go package main @@ -139,7 +144,8 @@ os.Stdout.Write(buf[:n]) } } - + +``` ### 删除文件 Go语言里面删除文件和删除文件夹是同一个函数 diff --git a/zh/07.6.md b/zh/07.6.md index b2f6fbce..e33a97f8 100644 --- a/zh/07.6.md +++ b/zh/07.6.md @@ -6,7 +6,9 @@ - func Contains(s, substr string) bool 字符串s中是否包含substr,返回bool值 - + +```Go + fmt.Println(strings.Contains("seafood", "foo")) fmt.Println(strings.Contains("seafood", "bar")) fmt.Println(strings.Contains("seafood", "")) @@ -17,43 +19,56 @@ //true //true +``` + - func Join(a []string, sep string) string 字符串链接,把slice a通过sep链接起来 - + +```Go + s := []string{"foo", "bar", "baz"} fmt.Println(strings.Join(s, ", ")) //Output:foo, bar, baz - +``` + - func Index(s, sep string) int 在字符串s中查找sep所在的位置,返回位置值,找不到返回-1 - + +```Go + fmt.Println(strings.Index("chicken", "ken")) fmt.Println(strings.Index("chicken", "dmr")) //Output:4 //-1 - +``` - func Repeat(s string, count int) string 重复s字符串count次,最后返回重复的字符串 - + +```Go + fmt.Println("ba" + strings.Repeat("na", 2)) //Output:banana - +``` - func Replace(s, old, new string, n int) string 在s字符串中,把old字符串替换为new字符串,n表示替换的次数,小于0表示全部替换 - + +```Go + fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2)) fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1)) //Output:oinky oinky oink //moo moo moo - +``` - func Split(s, sep string) []string 把s字符串按照sep分割,返回slice - + +```Go + fmt.Printf("%q\n", strings.Split("a,b,c", ",")) fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a ")) fmt.Printf("%q\n", strings.Split(" xyz ", "")) @@ -62,27 +77,35 @@ //["" "man " "plan " "canal panama"] //[" " "x" "y" "z" " "] //[""] +``` - func Trim(s string, cutset string) string 在s字符串的头部和尾部去除cutset指定的字符串 +```Go + fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! ")) //Output:["Achtung"] +``` - func Fields(s string) []string 去除s字符串的空格符,并且按照空格分割返回slice +```Go + fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz ")) //Output:Fields are: ["foo" "bar" "baz"] - +``` ## 字符串转换 字符串转化的函数在strconv中,如下也只是列出一些常用的: - Append 系列函数将整数等转换为字符串后,添加到现有的字节数组中。 +```Go + package main import ( @@ -98,8 +121,10 @@ str = strconv.AppendQuoteRune(str, '单') fmt.Println(string(str)) } +``` - Format 系列函数把其他类型的转换为字符串 +```Go package main @@ -117,8 +142,12 @@ fmt.Println(a, b, c, d, e) } +``` + - Parse 系列函数把字符串转换为其他类型 - + +```Go + package main import ( @@ -144,7 +173,7 @@ fmt.Println(a, b, c, d, e) } - +``` ## links * [目录]() diff --git a/zh/08.1.md b/zh/08.1.md index fc9a9ea8..369d8ce0 100644 --- a/zh/08.1.md +++ b/zh/08.1.md @@ -31,11 +31,15 @@ IPv6是下一版本的互联网协议,也可以说是下一代互联网的协 ### Go支持的IP类型 在Go的`net`包中定义了很多类型、函数和方法用来网络编程,其中IP的定义如下: +```Go type IP []byte +``` 在`net`包中有很多函数来操作IP,但是其中比较有用的也就几个,其中`ParseIP(s string) IP`函数会把一个IPv4或者IPv6的地址转化成IP类型,请看下面的例子: +```Go + package main import ( "net" @@ -57,6 +61,7 @@ IPv6是下一版本的互联网协议,也可以说是下一代互联网的协 os.Exit(0) } +``` 执行之后你就会发现只要你输入一个IP地址就会给出相应的IP格式 ## TCP Socket @@ -64,20 +69,29 @@ IPv6是下一版本的互联网协议,也可以说是下一代互联网的协 在Go语言的`net`包中有一个类型`TCPConn`,这个类型可以用来作为客户端和服务器端交互的通道,他有两个主要的函数: +```Go + func (c *TCPConn) Write(b []byte) (n int, err os.Error) func (c *TCPConn) Read(b []byte) (n int, err os.Error) +``` `TCPConn`可以用在客户端和服务器端来读写数据。 还有我们需要知道一个`TCPAddr`类型,他表示一个TCP的地址信息,他的定义如下: +```Go + type TCPAddr struct { IP IP Port int } +``` 在Go语言中通过`ResolveTCPAddr`获取一个`TCPAddr` +```Go + func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error) +``` - net参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only),TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个). - addr表示域名或者IP地址,例如"www.google.com:80" 或者"127.0.0.1:22". @@ -86,8 +100,10 @@ IPv6是下一版本的互联网协议,也可以说是下一代互联网的协 ### TCP client Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回一个`TCPConn`类型的对象,当连接建立时服务器端也创建一个同类型的对象,此时客户端和服务器段通过各自拥有的`TCPConn`对象来进行数据交换。一般而言,客户端通过`TCPConn`对象将请求信息发送到服务器端,读取服务器端响应的信息。服务器端读取并解析来自客户端的请求,并返回应答信息,这个连接只有当任一端关闭了连接之后才失效,不然这连接可以一直在使用。建立连接的函数定义如下: - func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error) +```Go + func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error) +``` - net参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only)、TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个) - laddr表示本机地址,一般设置为nil - raddr表示远程的服务地址 @@ -97,6 +113,7 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 "HEAD / HTTP/1.0\r\n\r\n" 从服务端接收到的响应信息格式可能如下: +```Go HTTP/1.0 200 OK ETag: "-9985996" @@ -105,8 +122,9 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 Connection: close Date: Sat, 28 Aug 2010 00:43:48 GMT Server: lighttpd/1.4.23 - +``` 我们的客户端代码如下所示: +```Go package main @@ -141,15 +159,18 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 } } +``` 通过上面的代码我们可以看出:首先程序将用户的输入作为参数`service`传入`net.ResolveTCPAddr`获取一个tcpAddr,然后把tcpAddr传入DialTCP后创建了一个TCP连接`conn`,通过`conn`来发送请求信息,最后通过`ioutil.ReadAll`从`conn`中读取全部的文本,也就是服务端响应反馈的信息。 ### TCP server 上面我们编写了一个TCP的客户端程序,也可以通过net包来创建一个服务器端程序,在服务器端我们需要绑定服务到指定的非激活端口,并监听此端口,当有客户端请求到达的时候可以接收到来自客户端连接的请求。net包中有相应功能的函数,函数定义如下: +```Go func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error) func (l *TCPListener) Accept() (c Conn, err os.Error) - +``` 参数说明同DialTCP的参数一样。下面我们实现一个简单的时间同步服务,监听7777端口 +```Go package main @@ -183,9 +204,11 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 } } +``` 上面的服务跑起来之后,它将会一直在那里等待,直到有新的客户端请求到达。当有新的客户端请求到达并同意接受`Accept`该请求的时候他会反馈当前的时间信息。值得注意的是,在代码中`for`循环里,当有错误发生时,直接continue而不是退出,是因为在服务器端跑代码的时候,当有错误发生的情况下最好是由服务端记录错误,然后当前连接的客户端直接报错而退出,从而不会影响到当前服务端运行的整个服务。 上面的代码有个缺点,执行的时候是单任务的,不能同时接收多个请求,那么该如何改造以使它支持多并发呢?Go里面有一个goroutine机制,请看下面改造后的代码 +```Go package main @@ -224,9 +247,11 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 } } +``` 通过把业务处理分离到函数`handleClient`,我们就可以进一步地实现多并发执行了。看上去是不是很帅,增加`go`关键词就实现了服务端的多并发,从这个小例子也可以看出goroutine的强大之处。 有的朋友可能要问:这个服务端没有处理客户端实际请求的内容。如果我们需要通过从客户端发送不同的请求来获取不同的时间格式,而且需要一个长连接,该怎么做呢?请看: +```Go package main @@ -287,35 +312,43 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 } } +``` 在上面这个例子中,我们使用`conn.Read()`不断读取客户端发来的请求。由于我们需要保持与客户端的长连接,所以不能在读取完一次请求后就关闭连接。由于`conn.SetReadDeadline()`设置了超时,当一定时间内客户端无请求发送,`conn`便会自动关闭,下面的for循环即会因为连接已关闭而跳出。需要注意的是,`request`在创建时需要指定一个最大长度以防止flood attack;每次读取到请求处理完毕后,需要清理request,因为`conn.Read()`会将新读取到的内容append到原内容之后。 ### 控制TCP连接 TCP有很多连接控制函数,我们平常用到比较多的有如下几个函数: +```Go func DialTimeout(net, addr string, timeout time.Duration) (Conn, error) +``` 设置建立连接的超时时间,客户端和服务器端都适用,当超过设置时间时,连接自动关闭。 +```Go func (c *TCPConn) SetReadDeadline(t time.Time) error func (c *TCPConn) SetWriteDeadline(t time.Time) error +``` 用来设置写入/读取一个连接的超时时间。当超过设置时间时,连接自动关闭。 +```Go func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error - -设置客户端是否和服务器端保持长连接,可以降低建立TCP连接时的握手开销,对于一些需要频繁交换数据的应用场景比较适用。 +``` +设置keepAlive属性,是操作系统层在tcp上没有数据和ACK的时候,会间隔性的发送keepalive包,操作系统可以通过该包来判断一个tcp连接是否已经断开,在windows上默认2个小时没有收到数据和keepalive包的时候人为tcp连接已经断开,这个功能和我们通常在应用层加的心跳包的功能类似。 更多的内容请查看`net`包的文档。 ## UDP Socket Go语言包中处理UDP Socket和TCP Socket不同的地方就是在服务器端处理多个客户端请求数据包的方式不同,UDP缺少了对客户端连接请求的Accept函数。其他基本几乎一模一样,只有TCP换成了UDP而已。UDP的几个主要函数如下所示: +```Go func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error) func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error) func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error) func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error) func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error) - +``` 一个UDP的客户端代码如下所示,我们可以看到不同的就是TCP换成了UDP而已: +```Go package main @@ -350,7 +383,9 @@ Go语言包中处理UDP Socket和TCP Socket不同的地方就是在服务器端 } } +``` 我们来看一下UDP服务器端如何来处理: +```Go package main @@ -387,6 +422,7 @@ Go语言包中处理UDP Socket和TCP Socket不同的地方就是在服务器端 } } +``` ## 总结 通过对TCP和UDP Socket编程的描述和实现,可见Go已经完备地支持了Socket编程,而且使用起来相当的方便,Go提供了很多函数,通过这些函数可以很容易就编写出高性能的Socket应用。 diff --git a/zh/08.2.md b/zh/08.2.md index 50c6eaea..085c2e71 100644 --- a/zh/08.2.md +++ b/zh/08.2.md @@ -45,6 +45,8 @@ Go语言标准包里面没有提供对WebSocket的支持,但是在由官方维 WebSocket分为客户端和服务端,接下来我们将实现一个简单的例子:用户输入信息,客户端通过WebSocket将信息发送给服务器端,服务器端收到信息之后主动Push信息到客户端,然后客户端将输出其收到的信息,客户端的代码如下: +```html + @@ -86,7 +88,7 @@ WebSocket分为客户端和服务端,接下来我们将实现一个简单的 - +``` 可以看到客户端JS,很容易的就通过WebSocket函数建立了一个与服务器的连接sock,当握手成功后,会触发WebScoket对象的onopen事件,告诉客户端连接已经成功建立。客户端一共绑定了四个事件。 - 1)onopen 建立连接后触发 @@ -96,6 +98,8 @@ WebSocket分为客户端和服务端,接下来我们将实现一个简单的 我们服务器端的实现如下: +```Go + package main import ( @@ -136,6 +140,7 @@ WebSocket分为客户端和服务端,接下来我们将实现一个简单的 } } +``` 当客户端将用户输入的信息Send之后,服务器端通过Receive接收到了相应信息,然后通过Send发送了应答信息。 ![](images/8.2.websocket3.png?raw=true) diff --git a/zh/08.3.md b/zh/08.3.md index 3bae24fb..25da1d15 100644 --- a/zh/08.3.md +++ b/zh/08.3.md @@ -62,6 +62,8 @@ Go没有为REST提供直接支持,但是因为RESTful是基于HTTP协议实现 我们现在可以通过`POST`里面增加隐藏字段`_method`这种方式可以来模拟`PUT`、`DELETE`等方式,但是服务器端需要做转换。我现在的项目里面就按照这种方式来做的REST接口。当然Go语言里面完全按照RESTful来实现是很容易的,我们通过下面的例子来说明如何实现RESTful的应用设计。 +```Go + package main import ( @@ -90,7 +92,7 @@ Go没有为REST提供直接支持,但是因为RESTful是基于HTTP协议实现 func adduser(w http.ResponseWriter, r *http.Request) { uid := r.FormValue("uid") - fmt.Fprint(w, "you are add user %s", uid) + fmt.Fprintf(w, "you are add user %s", uid) } func main() { @@ -103,6 +105,7 @@ Go没有为REST提供直接支持,但是因为RESTful是基于HTTP协议实现 http.ListenAndServe(":8088", nil) } +``` 上面的代码演示了如何编写一个REST的应用,我们访问的资源是用户,我们通过不同的method来访问不同的函数,这里使用了第三方库`github.com/drone/routes`,在前面章节我们介绍过如何实现自定义的路由器,这个库实现了自定义路由和方便的路由规则映射,通过它,我们可以很方便的实现REST的架构。通过上面的代码可知,REST就是根据不同的method访问同一个资源的时候实现不同的逻辑处理。 ## 总结 diff --git a/zh/08.4.md b/zh/08.4.md index d2da4fb2..086c2258 100644 --- a/zh/08.4.md +++ b/zh/08.4.md @@ -45,6 +45,8 @@ T、T1和T2类型必须能被`encoding/gob`包编解码。 ### HTTP RPC http的服务端代码实现如下: +```Go + package main import ( @@ -90,10 +92,13 @@ http的服务端代码实现如下: } } +``` 通过上面的例子可以看到,我们注册了一个Arith的RPC服务,然后通过`rpc.HandleHTTP`函数把该服务注册到了HTTP协议上,然后我们就可以利用http的方式来传递数据了。 请看下面的客户端代码: +```Go + package main import ( @@ -140,15 +145,19 @@ http的服务端代码实现如下: } +``` 我们把上面的服务端和客户端的代码分别编译,然后先把服务端开启,然后开启客户端,输入代码,就会输出如下信息: +```Go $ ./http_c localhost Arith: 17*8=136 Arith: 17/8=2 remainder 1 +``` 通过上面的调用可以看到参数和返回值是我们定义的struct类型,在服务端我们把它们当做调用函数的参数的类型,在客户端作为`client.Call`的第2,3两个参数的类型。客户端最重要的就是这个Call函数,它有3个参数,第1个要调用的函数的名字,第2个是要传递的参数,第3个要返回的参数(注意是指针类型),通过上面的代码例子我们可以发现,使用Go的RPC实现相当的简单,方便。 ### TCP RPC 上面我们实现了基于HTTP协议的RPC,接下来我们要实现基于TCP协议的RPC,服务端的实现代码如下所示: +```Go package main @@ -212,11 +221,14 @@ http的服务端代码实现如下: } } +``` 上面这个代码和http的服务器相比,不同在于:在此处我们采用了TCP协议,然后需要自己控制连接,当有客户端连接上来后,我们需要把这个连接交给rpc来处理。 如果你留心了,你会发现这它是一个阻塞型的单用户的程序,如果想要实现多并发,那么可以使用goroutine来实现,我们前面在socket小节的时候已经介绍过如何处理goroutine。 下面展现了TCP实现的RPC客户端: +```Go + package main import ( @@ -263,11 +275,14 @@ http的服务端代码实现如下: } +``` 这个客户端代码和http的客户端代码对比,唯一的区别一个是DialHTTP,一个是Dial(tcp),其他处理一模一样。 ### JSON RPC JSON RPC是数据编码采用了JSON,而不是gob编码,其他和上面介绍的RPC概念一模一样,下面我们来演示一下,如何使用Go提供的json-rpc标准包,请看服务端代码的实现: +```Go + package main import ( @@ -331,9 +346,11 @@ JSON RPC是数据编码采用了JSON,而不是gob编码,其他和上面介 } } +``` 通过示例我们可以看出 json-rpc是基于TCP协议实现的,目前它还不支持HTTP方式。 请看客户端的实现代码: +```Go package main @@ -380,7 +397,8 @@ JSON RPC是数据编码采用了JSON,而不是gob编码,其他和上面介 fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) } - + +``` ## 总结 Go已经提供了对RPC的良好支持,通过上面HTTP、TCP、JSON RPC的实现,我们就可以很方便的开发很多分布式的Web应用,我想作为读者的你已经领会到这一点。但遗憾的是目前Go尚未提供对SOAP RPC的支持,欣慰的是现在已经有第三方的开源实现了。 diff --git a/zh/09.1.md b/zh/09.1.md index baaed84f..cf09f470 100644 --- a/zh/09.1.md +++ b/zh/09.1.md @@ -49,9 +49,12 @@ CSRF的防御可以从服务端和客户端两方面着手,防御效果是从 接下来我就以Go语言来举例说明,如何限制对资源的访问方法: +```Go + mux.Get("/user/:uid", getuser) mux.Post("/user/:uid", modifyuser) +``` 这样处理后,因为我们限定了修改只能使用POST,当GET方式请求时就拒绝响应,所以上面图示中GET方式的CSRF攻击就可以防止了,但这样就能全部解决问题了吗?当然不是,因为POST也是可以模拟的。 因此我们需要实施第二步,在非GET方式的请求中增加随机数,这个大概有三种方式来进行: @@ -62,6 +65,8 @@ CSRF的防御可以从服务端和客户端两方面着手,防御效果是从 生成随机数token +```Go + h := md5.New() io.WriteString(h, strconv.FormatInt(crutime, 10)) io.WriteString(h, "ganraomaxxxxxxxxx") @@ -70,12 +75,17 @@ CSRF的防御可以从服务端和客户端两方面着手,防御效果是从 t, _ := template.ParseFiles("login.gtpl") t.Execute(w, token) +``` 输出token +```html +``` 验证token +```Go + r.ParseForm() token := r.Form.Get("token") if token != "" { @@ -83,7 +93,8 @@ CSRF的防御可以从服务端和客户端两方面着手,防御效果是从 } else { //不存在token报错 } - + +``` 这样基本就实现了安全的POST,但是也许你会说如果破解了token的算法呢,按照理论上是,但是实际上破解是基本不可能的,因为有人曾计算过,暴力破解该串大概需要2的11次方时间。 ## 总结 diff --git a/zh/09.2.md b/zh/09.2.md index e3b1d55a..14ea04d9 100644 --- a/zh/09.2.md +++ b/zh/09.2.md @@ -31,6 +31,7 @@ - 加入检查及阻止来自外部数据源的变量命名为CleanMap。 接下来,让我们通过一个例子来巩固这些概念,请看下面这个表单 +```html
我是谁: @@ -42,7 +43,9 @@
+``` 在处理这个表单的编程逻辑中,非常容易犯的错误是认为只能提交三个选择中的一个。其实攻击者可以模拟POST操作,递交`name=attack`这样的数据,所以在此时我们需要做类似白名单的处理 +```Go r.ParseForm() name := r.Form.Get("name") @@ -51,18 +54,21 @@ CleanMap["name"] = name } +``` 上面代码中我们初始化了一个CleanMap的变量,当判断获取的name是`astaxie`、`herry`、`marry`三个中的一个之后 ,我们把数据存储到了CleanMap之中,这样就可以确保CleanMap["name"]中的数据是合法的,从而在代码的其它部分使用它。当然我们还可以在else部分增加非法数据的处理,一种可能是再次显示表单并提示错误。但是不要试图为了友好而输出被污染的数据。 上面的方法对于过滤一组已知的合法值的数据很有效,但是对于过滤有一组已知合法字符组成的数据时就没有什么帮助。例如,你可能需要一个用户名只能由字母及数字组成: +```Go r.ParseForm() username := r.Form.Get("username") CleanMap := make(map[string]interface{}, 0) - if ok, _ := regexp.MatchString("^[a-zA-Z0-9].$", username); ok { + if ok, _ := regexp.MatchString("^[a-zA-Z0-9]+$", username); ok { CleanMap["username"] = username } - + +``` ## 总结 数据过滤在Web安全中起到一个基石的作用,大多数的安全问题都是由于没有过滤数据和验证数据引起的,例如前面小节的CSRF攻击,以及接下来将要介绍的XSS攻击、SQL注入等都是没有认真地过滤数据引起的,因此我们需要特别重视这部分的内容。 diff --git a/zh/09.3.md b/zh/09.3.md index 168736a1..d778a70c 100644 --- a/zh/09.3.md +++ b/zh/09.3.md @@ -38,11 +38,13 @@ Web应用未对用户提交请求的数据做充分的检查过滤,允许用 - 使用HTTP头指定类型 +```Go + `w.Header().Set("Content-Type","text/javascript")` 这样就可以让浏览器解析javascript代码,而不会是html输出。 - +``` ## 总结 XSS漏洞是相当有危害的,在开发Web应用的时候,一定要记住过滤数据,特别是在输出到客户端之前,这是现在行之有效的防止XSS的手段。 diff --git a/zh/09.4.md b/zh/09.4.md index 141323ed..d576a168 100644 --- a/zh/09.4.md +++ b/zh/09.4.md @@ -9,6 +9,7 @@ SQL注入攻击(SQL Injection),简称注入攻击,是Web开发中最常 下面将通过一些真实的例子来详细讲解SQL注入的方式。 考虑以下简单的登录表单: +```html

Username:

@@ -16,31 +17,39 @@ SQL注入攻击(SQL Injection),简称注入攻击,是Web开发中最常

+``` 我们的处理里面的SQL可能是这样的: +```Go username:=r.Form.Get("username") password:=r.Form.Get("password") sql:="SELECT * FROM user WHERE username='"+username+"' AND password='"+password+"'" +``` 如果用户的输入的用户名如下,密码任意 +```Go myuser' or 'foo' = 'foo' -- +``` 那么我们的SQL变成了如下所示: +```Go SELECT * FROM user WHERE username='myuser' or 'foo'=='foo' --'' AND password='xxx' - +``` 在SQL里面`--`是注释标记,所以查询语句会在此中断。这就让攻击者在不知道任何合法用户名和密码的情况下成功登录了。 对于MSSQL还有更加危险的一种SQL注入,就是控制系统,下面这个可怕的例子将演示如何在某些版本的MSSQL数据库上执行系统命令。 +```Go sql:="SELECT * FROM products WHERE name LIKE '%"+prod+"%'" Db.Exec(sql) - +``` 如果攻击提交`a%' exec master..xp_cmdshell 'net user test testpass /ADD' --`作为变量 prod的值,那么sql将会变成 +```Go sql:="SELECT * FROM products WHERE name LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'" - +``` MSSQL服务器会执行这条SQL语句,包括它后面那个用于向系统添加新用户的命令。如果这个程序是以sa运行而 MSSQLSERVER服务又有足够的权限的话,攻击者就可以获得一个系统帐号来访问主机了。 >虽然以上的例子是针对某一特定的数据库系统的,但是这并不代表不能对其它数据库系统实施类似的攻击。针对这种安全漏洞,只要使用不同方法,各种数据库都有可能遭殃。 diff --git a/zh/09.5.md b/zh/09.5.md index 2ced04ee..fedf7145 100644 --- a/zh/09.5.md +++ b/zh/09.5.md @@ -7,6 +7,7 @@ 目前用的最多的密码存储方案是将明文密码做单向哈希后存储,单向哈希算法有一个特征:无法通过哈希后的摘要(digest)恢复原始数据,这也是“单向”二字的来源。常用的单向哈希算法包括SHA-256, SHA-1, MD5等。 Go语言对这三种加密算法的实现如下所示: +```Go //import "crypto/sha256" h := sha256.New() @@ -23,6 +24,7 @@ Go语言对这三种加密算法的实现如下所示: io.WriteString(h, "需要加密的密码") fmt.Printf("%x", h.Sum(nil)) +``` 单向哈希有两个特性: - 1)同一个密码进行单向哈希,得到的总是唯一确定的摘要。 @@ -40,6 +42,8 @@ Go语言对这三种加密算法的实现如下所示: 没有攻不破的盾,但也没有折不断的矛。现在安全性比较好的网站,都会用一种叫做“加盐”的方式来存储密码,也就是常说的 “salt”。他们通常的做法是,先将用户输入的密码进行一次MD5(或其它哈希算法)加密;将得到的 MD5 值前后加上一些只有管理员自己知道的随机串,再进行一次MD5加密。这个随机串中可以包括某些固定的串,也可以包括用户名(用来保证每个用户加密使用的密钥都不一样)。 +```Go + //import "crypto/md5" //假设用户名abc,密码123456 h := md5.New() @@ -60,6 +64,7 @@ Go语言对这三种加密算法的实现如下所示: last :=fmt.Sprintf("%x", h.Sum(nil)) +``` 在两个salt没有泄露的情况下,黑客如果拿到的是最后这个加密串,就几乎不可能推算出原始的密码是什么了。 ## 专家方案 @@ -72,9 +77,10 @@ Go语言对这三种加密算法的实现如下所示: 这里推荐`scrypt`方案,scrypt是由著名的FreeBSD黑客Colin Percival为他的备份服务Tarsnap开发的。 目前Go语言里面支持的库http://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt +```Go dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32) - +``` 通过上面的的方法可以获取唯一的相应的密码值,这是目前为止最难破解的。 ## 总结 diff --git a/zh/09.6.md b/zh/09.6.md index deee4540..b4a69fc9 100644 --- a/zh/09.6.md +++ b/zh/09.6.md @@ -4,6 +4,8 @@ ## base64加解密 如果Web应用足够简单,数据的安全性没有那么严格的要求,那么可以采用一种比较简单的加解密方法是`base64`,这种方式实现起来比较简单,Go语言的`base64`包已经很好的支持了这个,请看下面的例子: +```Go + package main import ( @@ -37,7 +39,7 @@ fmt.Println(string(enbyte)) } - +``` ## 高级加解密 Go语言的`crypto`里面支持对称加密的高级加解密包有: @@ -46,6 +48,7 @@ Go语言的`crypto`里面支持对称加密的高级加解密包有: - `crypto/des`包:DES(Data Encryption Standard),是一种对称加密标准,是目前使用最广泛的密钥系统,特别是在保护金融数据的安全中。曾是美国联邦政府的加密标准,但现已被AES所替代。 因为这两种算法使用方法类似,所以在此,我们仅用aes包为例来讲解它们的使用,请看下面的例子 +```Go package main @@ -94,9 +97,11 @@ Go语言的`crypto`里面支持对称加密的高级加解密包有: fmt.Printf("%x=>%s\n", ciphertext, plaintextCopy) } - +``` 上面通过调用函数`aes.NewCipher`(参数key必须是16、24或者32位的[]byte,分别对应AES-128, AES-192或AES-256算法),返回了一个`cipher.Block`接口,这个接口实现了三个功能: +```Go + type Block interface { // BlockSize returns the cipher's block size. BlockSize() int @@ -109,7 +114,7 @@ Go语言的`crypto`里面支持对称加密的高级加解密包有: // Dst and src may point at the same memory. Decrypt(dst, src []byte) } - +``` 这三个函数实现了加解密操作,详细的操作请看上面的例子。 ## 总结 diff --git a/zh/10.1.md b/zh/10.1.md index d127271d..5da95430 100644 --- a/zh/10.1.md +++ b/zh/10.1.md @@ -19,6 +19,7 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三 - 有利于搜索引擎抓取,能够提高站点的SEO 我们可以通过下面的代码来实现域名的对应locale: +```Go if r.Host == "www.asta.com" { i18n.SetLocale("en") @@ -27,8 +28,9 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三 } else if r.Host == "www.asta.tw" { i18n.SetLocale("zh-TW") } - +``` 当然除了整域名设置地区之外,我们还可以通过子域名来设置地区,例如"en.asta.com"表示英文站点,"cn.asta.com"表示中文站点。实现代码如下所示: +```Go prefix := strings.Split(r.Host,".") @@ -39,7 +41,7 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三 } else if prefix[0] == "tw" { i18n.SetLocale("zh-TW") } - +``` 通过域名设置Locale有如上所示的优点,但是我们一般开发Web应用的时候不会采用这种方式,因为首先域名成本比较高,开发一个Locale就需要一个域名,而且往往统一名称的域名不一定能申请的到,其次我们不愿意为每个站点去本地化一个配置,而更多的是采用url后面带参数的方式,请看下面的介绍。 ### 从域名参数设置Locale @@ -48,15 +50,17 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三 这种设置方式几乎拥有前面讲的通过域名设置Locale的所有优点,它采用RESTful的方式,以使得我们不需要增加额外的方法来处理。但是这种方式需要在每一个的link里面增加相应的参数locale,这也许有点复杂而且有时候甚至相当的繁琐。不过我们可以写一个通用的函数url,让所有的link地址都通过这个函数来生成,然后在这个函数里面增加`locale=params["locale"]`参数来缓解一下。 也许我们希望URL地址看上去更加的RESTful一点,例如:www.asta.com/en/books(英文站点)和www.asta.com/zh/books(中文站点),这种方式的URL更加有利于SEO,而且对于用户也比较友好,能够通过URL直观的知道访问的站点。那么这样的URL地址可以通过router来获取locale(参考REST小节里面介绍的router插件实现): +```Go mux.Get("/:locale/books", listbook) - +``` ### 从客户端设置地区 在一些特殊的情况下,我们需要根据客户端的信息而不是通过URL来设置Locale,这些信息可能来自于客户端设置的喜好语言(浏览器中设置),用户的IP地址,用户在注册的时候填写的所在地信息等。这种方式比较适合Web为基础的应用。 - Accept-Language 客户端请求的时候在HTTP头信息里面有`Accept-Language`,一般的客户端都会设置该信息,下面是Go语言实现的一个简单的根据`Accept-Language`实现设置地区的代码: +```Go AL := r.Header.Get("Accept-Language") if AL == "en" { @@ -66,7 +70,7 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三 } else if AL == "zh-TW" { i18n.SetLocale("zh-TW") } - +``` 当然在实际应用中,可能需要更加严格的判断来进行设置地区 - IP地址 diff --git a/zh/10.2.md b/zh/10.2.md index e3ed7a19..5ba4aaa4 100644 --- a/zh/10.2.md +++ b/zh/10.2.md @@ -4,6 +4,8 @@ ## 本地化文本消息 文本信息是编写Web应用中最常用到的,也是本地化资源中最多的信息,想要以适合本地语言的方式来显示文本信息,可行的一种方案是:建立需要的语言相应的map来维护一个key-value的关系,在输出之前按需从适合的map中去获取相应的文本,如下是一个简单的示例: +```Go + package main import "fmt" @@ -34,16 +36,17 @@ return "" } - +``` 上面示例演示了不同locale的文本翻译,实现了中文和英文对于同一个key显示不同语言的实现,上面实现了中文的文本消息,如果想切换到英文版本,只需要把lang设置为en即可。 有些时候仅是key-value替换是不能满足需要的,例如"I am 30 years old",中文表达是"我今年30岁了",而此处的30是一个变量,该怎么办呢?这个时候,我们可以结合`fmt.Printf`函数来实现,请看下面的代码: +```Go en["how old"] ="I am %d years old" cn["how old"] ="我今年%d岁了" fmt.Printf(msg(lang, "how old"), 30) - +``` 上面的示例代码仅用以演示内部的实现方案,而实际数据是存储在JSON里面的,所以我们可以通过`json.Unmarshal`来为相应的map填充数据。 ## 本地化日期和时间 @@ -54,6 +57,8 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义,为了获得对应于当前locale的时间,我们应首先使用`time.LoadLocation(name string)`获取相应于地区的locale,比如`Asia/Shanghai`或`America/Chicago`对应的时区信息,然后再利用此信息与调用`time.Now`获得的Time对象协作来获得最终的时间。详细的请看下面的例子(该例子采用上面例子的一些变量): +```Go + en["time_zone"]="America/Chicago" cn["time_zone"]="Asia/Shanghai" @@ -62,7 +67,9 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义,为 t = t.In(loc) fmt.Println(t.Format(time.RFC3339)) +``` 我们可以通过类似处理文本格式的方式来解决时间格式的问题,举例如下: +```Go en["date_format"]="%Y-%m-%d %H:%M:%S" cn["date_format"]="%Y年%m月%d日 %H时%M分%S秒" @@ -78,8 +85,10 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义,为 //%d 替换成24 } +``` ## 本地化货币值 各个地区的货币表示也不一样,处理方式也与日期差不多,细节请看下面代码: +```Go en["money"] ="USD %d" cn["money"] ="¥%d元" @@ -90,9 +99,10 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义,为 return fmt.Sprintf(fomate,money) } - +``` ## 本地化视图和资源 我们可能会根据Locale的不同来展示视图,这些视图包含不同的图片、css、js等各种静态资源。那么应如何来处理这些信息呢?首先我们应按locale来组织文件信息,请看下面的文件目录安排: +```html views |--en //英文模板 @@ -108,14 +118,16 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义,为 index.tpl login.tpl +``` 有了这个目录结构后我们就可以在渲染的地方这样来实现代码: - +```Go s1, _ := template.ParseFiles("views"+lang+"index.tpl") VV.Lang=lang s1.Execute(os.Stdout, VV) - +``` 而对于里面的index.tpl里面的资源设置如下: +```html // js文件 @@ -123,7 +135,7 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义,为 // 图片文件 - +``` 采用这种方式来本地化视图以及资源时,我们就可以很容易的进行扩展了。 ## 总结 diff --git a/zh/10.3.md b/zh/10.3.md index 2f702a65..5b8d383d 100644 --- a/zh/10.3.md +++ b/zh/10.3.md @@ -3,6 +3,7 @@ 前面小节介绍了如何处理本地化资源,即Locale一个相应的配置文件,那么如果处理多个的本地化资源呢?而对于一些我们经常用到的例如:简单的文本翻译、时间日期、数字等如果处理呢?本小节将一一解决这些问题。 ## 管理多个本地包 在开发一个应用的时候,首先我们要决定是只支持一种语言,还是多种语言,如果要支持多种语言,我们则需要制定一个组织结构,以方便将来更多语言的添加。在此我们设计如下:Locale有关的文件放置在config/locales下,假设你要支持中文和英文,那么你需要在这个文件夹下放置en.json和zh.json。大概的内容如下所示: +```json # zh.json @@ -22,22 +23,26 @@ } } +``` 为了支持国际化,在此我们使用了一个国际化相关的包——[go-i18n](https://github.com/astaxie/go-i18n),首先我们向go-i18n包注册config/locales这个目录,以加载所有的locale文件 +```Go Tr:=i18n.NewLocale() Tr.LoadPath("config/locales") +``` 这个包使用起来很简单,你可以通过下面的方式进行测试: +```Go fmt.Println(Tr.Translate("submit")) //输出Submit Tr.SetLocale("zn") fmt.Println(Tr.Translate("submit")) //输出“递交” - +``` ## 自动加载本地包 上面我们介绍了如何自动加载自定义语言包,其实go-i18n库已经预加载了很多默认的格式信息,例如时间格式、货币格式,用户可以在自定义配置时改写这些默认配置,请看下面的处理过程: - +```Go //加载默认配置文件,这些文件都放在go-i18n/locales下面 @@ -83,7 +88,9 @@ return nil } +``` 通过上面的方法加载配置信息到默认的文件,这样我们就可以在我们没有自定义时间信息的时候执行如下的代码获取对应的信息: +```Go //locale=zh的情况下,执行如下代码: @@ -95,13 +102,14 @@ fmt.Println(Tr.Money(11.11)) //输出:¥11.11 - +``` ## template mapfunc 上面我们实现了多个语言包的管理和加载,而一些函数的实现是基于逻辑层的,例如:"Tr.Translate"、"Tr.Time"、"Tr.Money"等,虽然我们在逻辑层可以利用这些函数把需要的参数进行转换后在模板层渲染的时候直接输出,但是如果我们想在模版层直接使用这些函数该怎么实现呢?不知你是否还记得,在前面介绍模板的时候说过:Go语言的模板支持自定义模板函数,下面是我们实现的方便操作的mapfunc: 1. 文本信息 文本信息调用`Tr.Translate`来实现相应的信息转换,mapFunc的实现如下: +```Go func I18nT(args ...interface{}) string { ok := false @@ -115,18 +123,22 @@ return Tr.Translate(s) } +``` 注册函数如下: +```Go t.Funcs(template.FuncMap{"T": I18nT}) - +``` 模板中使用如下: +```Go {{.V.Submit | T}} - +``` 2. 时间日期 时间日期调用`Tr.Time`函数来实现相应的时间转换,mapFunc的实现如下: +```Go func I18nTimeDate(args ...interface{}) string { ok := false @@ -139,18 +151,21 @@ } return Tr.Time(s) } - +``` 注册函数如下: +```Go t.Funcs(template.FuncMap{"TD": I18nTimeDate}) - +``` 模板中使用如下: +```Go {{.V.Now | TD}} - +``` 3. 货币信息 货币调用`Tr.Money`函数来实现相应的时间转换,mapFunc的实现如下: +```Go func I18nMoney(args ...interface{}) string { ok := false @@ -163,15 +178,17 @@ } return Tr.Money(s) } - +``` 注册函数如下: +```Go t.Funcs(template.FuncMap{"M": I18nMoney}) - +``` 模板中使用如下: +```Go {{.V.Money | M}} - +``` ## 总结 通过这小节我们知道了如何实现一个多语言包的Web应用,通过自定义语言包我们可以方便的实现多语言,而且通过配置文件能够非常方便的扩充多语言,默认情况下,go-i18n会自定加载一些公共的配置信息,例如时间、货币等,我们就可以非常方便的使用,同时为了支持在模板中使用这些函数,也实现了相应的模板函数,这样就允许我们在开发Web应用的时候直接在模板中通过pipeline的方式来操作多语言包。 diff --git a/zh/11.1.md b/zh/11.1.md index 26c754f2..deaecf53 100644 --- a/zh/11.1.md +++ b/zh/11.1.md @@ -1,24 +1,28 @@ # 11.1 错误处理 Go语言主要的设计准则是:简洁、明白,简洁是指语法和C类似,相当的简单,明白是指任何语句都是很明显的,不含有任何隐含的东西,在错误处理方案的设计中也贯彻了这一思想。我们知道在C语言里面是通过返回-1或者NULL之类的信息来表示错误,但是对于使用者来说,不查看相应的API说明文档,根本搞不清楚这个返回值究竟代表什么意思,比如:返回0是成功,还是失败,而Go定义了一个叫做error的类型,来显式表达错误。在使用时,通过把返回的error变量与nil的比较,来判定操作是否成功。例如`os.Open`函数在打开文件失败时将返回一个不为nil的error变量 +```Go func Open(name string) (file *File, err error) - +``` 下面这个例子通过调用`os.Open`打开一个文件,如果出现错误,那么就会调用`log.Fatal`来输出错误信息: +```Go f, err := os.Open("filename.ext") if err != nil { log.Fatal(err) } - +``` 类似于`os.Open`函数,标准包中所有可能出错的API都会返回一个error变量,以方便错误处理,这个小节将详细地介绍error类型的设计,和讨论开发Web应用中如何更好地处理error。 ## Error类型 error类型是一个接口类型,这是它的定义: +```Go type error interface { Error() string } - +``` error是一个内置的接口类型,我们可以在/builtin/包下面找到相应的定义。而我们在很多内部包里面用到的 error是errors包下面的实现的私有结构errorString +```Go // errorString is a trivial implementation of error. type errorString struct { @@ -28,15 +32,17 @@ error是一个内置的接口类型,我们可以在/builtin/包下面找到相 func (e *errorString) Error() string { return e.s } - +``` 你可以通过`errors.New`把一个字符串转化为errorString,以得到一个满足接口error的对象,其内部实现如下: +```Go // New returns an error that formats as the given text. func New(text string) error { return &errorString{text} } - +``` 下面这个例子演示了如何使用`errors.New`: +```Go func Sqrt(f float64) (float64, error) { if f < 0 { @@ -44,16 +50,18 @@ error是一个内置的接口类型,我们可以在/builtin/包下面找到相 } // implementation } - +``` 在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码: +```Go f, err := Sqrt(-1) if err != nil { fmt.Println(err) } - +``` ## 自定义Error 通过上面的介绍我们知道error是一个interface,所以在实现自己的包的时候,通过定义实现此接口的结构,我们就可以实现自己的错误定义,请看来自Json包的示例: +```Go type SyntaxError struct { msg string // 错误描述 @@ -61,8 +69,9 @@ error是一个内置的接口类型,我们可以在/builtin/包下面找到相 } func (e *SyntaxError) Error() string { return e.msg } - +``` Offset字段在调用Error的时候不会被打印,但是我们可以通过类型断言获取错误类型,然后可以打印相应的错误信息,请看下面的例子: +```Go if err := dec.Decode(&val); err != nil { if serr, ok := err.(*json.SyntaxError); ok { @@ -71,8 +80,9 @@ Offset字段在调用Error的时候不会被打印,但是我们可以通过类 } return err } - +``` 需要注意的是,函数返回自定义错误时,返回值推荐设置为error类型,而非自定义错误类型,特别需要注意的是不应预声明自定义错误类型的变量。例如: +```Go func Decode() *SyntaxError { // 错误,将可能导致上层调用者err!=nil的判断永远为true。 var err *SyntaxError // 预声明错误变量 @@ -81,10 +91,11 @@ Offset字段在调用Error的时候不会被打印,但是我们可以通过类 } return err // 错误,err永远等于非nil,导致上层调用者err!=nil的判断始终为true } - +``` 原因见 http://golang.org/doc/faq#nil_error 上面例子简单的演示了如何自定义Error类型。但是如果我们还需要更复杂的错误处理呢?此时,我们来参考一下net包采用的方法: +```Go package net @@ -94,7 +105,9 @@ Offset字段在调用Error的时候不会被打印,但是我们可以通过类 Temporary() bool // Is the error temporary? } +``` 在调用的地方,通过类型断言err是不是net.Error,来细化错误的处理,例如下面的例子,如果一个网络发生临时性错误,那么将会sleep 1秒之后重试: +```Go if nerr, ok := err.(net.Error); ok && nerr.Temporary() { time.Sleep(1e9) @@ -103,11 +116,12 @@ Offset字段在调用Error的时候不会被打印,但是我们可以通过类 if err != nil { log.Fatal(err) } - +``` ## 错误处理 Go在错误处理上采用了与C类似的检查返回值的方式,而不是其他多数主流语言采用的异常方式,这造成了代码编写上的一个很大的缺点:错误处理代码的冗余,对于这种情况是我们通过复用检测函数来减少类似的代码。 请看下面这个例子代码: +```Go func init() { http.HandleFunc("/view", viewRecord) @@ -125,8 +139,9 @@ Go在错误处理上采用了与C类似的检查返回值的方式,而不是 http.Error(w, err.Error(), 500) } } - +``` 上面的例子中获取数据和模板展示调用时都有检测错误,当有错误发生时,调用了统一的处理函数`http.Error`,返回给客户端500错误码,并显示相应的错误数据。但是当越来越多的HandleFunc加入之后,这样的错误处理逻辑代码就会越来越多,其实我们可以通过自定义路由器来缩减代码(实现的思路可以参考第三章的HTTP详解)。 +```Go type appHandler func(http.ResponseWriter, *http.Request) error @@ -135,14 +150,16 @@ Go在错误处理上采用了与C类似的检查返回值的方式,而不是 http.Error(w, err.Error(), 500) } } - +``` 上面我们定义了自定义的路由器,然后我们可以通过如下方式来注册函数: +```Go func init() { http.Handle("/view", appHandler(viewRecord)) } - +``` 当请求/view的时候我们的逻辑处理可以变成如下代码,和第一种实现方式相比较已经简单了很多。 +```Go func viewRecord(w http.ResponseWriter, r *http.Request) error { c := appengine.NewContext(r) @@ -153,16 +170,18 @@ Go在错误处理上采用了与C类似的检查返回值的方式,而不是 } return viewTemplate.Execute(w, record) } - +``` 上面的例子错误处理的时候所有的错误返回给用户的都是500错误码,然后打印出来相应的错误代码,其实我们可以把这个错误信息定义的更加友好,调试的时候也方便定位问题,我们可以自定义返回的错误类型: +```Go type appError struct { Error error Message string Code int } - +``` 这样我们的自定义路由器可以改成如下方式: +```Go type appHandler func(http.ResponseWriter, *http.Request) *appError @@ -173,8 +192,9 @@ Go在错误处理上采用了与C类似的检查返回值的方式,而不是 http.Error(w, e.Message, e.Code) } } - +``` 这样修改完自定义错误之后,我们的逻辑处理可以改成如下方式: +```Go func viewRecord(w http.ResponseWriter, r *http.Request) *appError { c := appengine.NewContext(r) @@ -188,7 +208,7 @@ Go在错误处理上采用了与C类似的检查返回值的方式,而不是 } return nil } - +``` 如上所示,在我们访问view的时候可以根据不同的情况获取不同的错误码和错误信息,虽然这个和第一个版本的代码量差不多,但是这个显示的错误更加明显,提示的错误信息更加友好,扩展性也比第一个更好。 ## 总结 diff --git a/zh/11.2.md b/zh/11.2.md index 2e91a4a3..6677c824 100644 --- a/zh/11.2.md +++ b/zh/11.2.md @@ -1,6 +1,8 @@ # 11.2 使用GDB调试 开发程序过程中调试代码是开发者经常要做的一件事情,Go语言不像PHP、Python等动态语言,只要修改不需要编译就可以直接输出,而且可以动态的在运行环境下打印数据。当然Go语言也可以通过Println之类的打印数据来调试,但是每次都需要重新编译,这是一件相当麻烦的事情。我们知道在Python中有pdb/ipdb之类的工具调试,Javascript也有类似工具,这些工具都能够动态的显示变量信息,单步调试等。不过庆幸的是Go也有类似的工具支持:GDB。Go内部已经内置支持了GDB,所以,我们可以通过GDB来进行调试,那么本小节就来介绍一下如何通过GDB来调试Go程序。 +另外建议纯go代码使用[delve](https://github.com/derekparker/delve)可以很好的进行Go代码调试 + ## GDB调试简介 GDB是FSF(自由软件基金会)发布的一个强大的类UNIX系统下的程序调试工具。使用GDB可以做如下事情: @@ -94,6 +96,7 @@ GDB的一些常用命令如下所示 ## 调试过程 我们通过下面这个代码来演示如何通过GDB来调试Go程序,下面是将要演示的代码: +```Go package main @@ -120,7 +123,7 @@ GDB的一些常用命令如下所示 fmt.Println("count:", count) } } - +``` 编译文件,生成可执行文件gdbfile: go build -gcflags "-N -l" gdbfile.go diff --git a/zh/11.3.md b/zh/11.3.md index 8a506307..b8f1ab1c 100644 --- a/zh/11.3.md +++ b/zh/11.3.md @@ -3,6 +3,13 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`命令来实现单元测试和性能测试,`testing`框架和其他语言中的测试框架类似,你可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例,那么接下来让我们一一来看一下怎么写。 +另外建议安装[gotests](https://github.com/cweill/gotests)插件自动生成测试代码: + +```Go + go get -u -v github.com/cweill/gotests/... + +``` + ## 如何编写测试用例 由于`go test`命令只能在一个相应的目录下执行所有文件,所以我们接下来新建一个项目目录`gotest`,这样我们所有的代码和测试代码都在这个目录下。 @@ -10,6 +17,8 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` 1. gotest.go:这个文件里面我们是创建了一个包,里面有一个函数实现了除法运算: +```Go + package gotest import ( @@ -24,6 +33,8 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` return a / b, nil } +``` + 2. gotest_test.go:这是我们的单元测试文件,但是记住下面的这些原则: - 文件名必须是`_test.go`结尾的,这样在执行`go test`的时候才会执行到相应的代码 @@ -36,6 +47,8 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` 下面是我们的测试用例的代码: +```Go + package gotest import ( @@ -54,6 +67,8 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` t.Error("就是不通过") } +``` + 我们在项目目录下面执行`go test`,就会显示如下信息: --- FAIL: Test_Division_2 (0.00 seconds) @@ -73,7 +88,9 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` exit status 1 FAIL gotest 0.012s 上面的输出详细的展示了这个测试的过程,我们看到测试函数1`Test_Division_1`测试通过,而测试函数2`Test_Division_2`测试失败了,最后得出结论测试不通过。接下来我们把测试函数2修改成如下代码: - + +```Go + func Test_Division_2(t *testing.T) { if _, e := Division(6, 0); e == nil { //try a unit test on function t.Error("Division did not work as expected.") // 如果不是如预期的那么就报错 @@ -81,6 +98,7 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` t.Log("one test passed.", e) //记录一些你期望记录的信息 } } +``` 然后我们执行`go test -v`,就显示如下信息,测试通过了: === RUN Test_Division_1 @@ -97,14 +115,18 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` - 压力测试用例必须遵循如下格式,其中XXX可以是任意字母数字的组合,但是首字母不能是小写字母 +```Go func BenchmarkXXX(b *testing.B) { ... } - +``` + - `go test`不会默认执行压力测试的函数,如果要执行压力测试需要带上参数`-test.bench`,语法:`-test.bench="test_name_regex"`,例如`go test -test.bench=".*"`表示测试全部的压力测试函数 - 在压力测试用例中,请记得在循环体内使用`testing.B.N`,以使测试可以正常的运行 - 文件名也必须以`_test.go`结尾 下面我们新建一个压力测试文件webbench_test.go,代码如下所示: +```Go + package gotest import ( @@ -129,6 +151,7 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` } } +``` 我们执行命令`go test -file webbench_test.go -test.bench=".*"`,可以看到如下结果: diff --git a/zh/12.1.md b/zh/12.1.md index 8f039b3e..8e1ef6b3 100644 --- a/zh/12.1.md +++ b/zh/12.1.md @@ -1,6 +1,8 @@ # 12.1 应用日志 我们期望开发的Web应用程序能够把整个程序运行过程中出现的各种事件一一记录下来,Go语言中提供了一个简易的log包,我们使用该包可以方便的实现日志记录的功能,这些日志都是基于fmt包的打印再结合panic之类的函数来进行一般的打印、抛出错误处理。Go目前标准包只是包含了简单的功能,如果我们想把我们的应用日志保存到文件,然后又能够结合日志实现很多复杂的功能(编写过Java或者C++的读者应该都使用过log4j和log4cpp之类的日志工具),可以使用第三方开发的一个日志系统,`https://github.com/cihub/seelog`,它实现了很强大的日志功能。接下来我们介绍如何通过该日志系统来实现我们应用的日志功能。 +[logrus](https://github.com/sirupsen/logrus)是另外一个不错的日志系统,结合自己项目选择 + ## seelog介绍 seelog是用Go语言实现的一个日志系统,它提供了一些简单的函数来实现复杂的日志分配、过滤和格式化。主要有如下特性: @@ -18,10 +20,13 @@ seelog是用Go语言实现的一个日志系统,它提供了一些简单的函 上面只列举了部分特性,seelog是一个特别强大的日志处理系统,详细的内容请参看官方wiki。接下来我将简要介绍一下如何在项目中使用它: 首先安装seelog +```Go go get -u github.com/cihub/seelog - + +``` 然后我们来看一个简单的例子: +```Go package main @@ -32,10 +37,12 @@ seelog是用Go语言实现的一个日志系统,它提供了一些简单的函 log.Info("Hello from Seelog!") } +``` 编译后运行如果出现了`Hello from seelog`,说明seelog日志系统已经成功安装并且可以正常运行了。 ## 基于seelog的自定义日志处理 seelog支持自定义日志处理,下面是我基于它自定义的日志处理包的部分内容: +```Go package logs @@ -90,7 +97,7 @@ seelog支持自定义日志处理,下面是我基于它自定义的日志处 func UseLogger(newLogger seelog.LoggerInterface) { Logger = newLogger } - +``` 上面主要实现了三个函数, - `DisableLog` @@ -116,6 +123,7 @@ seelog支持自定义日志处理,下面是我基于它自定义的日志处 设置当前的日志器为相应的日志处理 上面我们定义了一个自定义的日志处理包,下面就是使用示例: +```Go package main @@ -132,29 +140,32 @@ seelog支持自定义日志处理,下面是我基于它自定义的日志处 err := http.ListenAndServe(addr, routes.NewMux()) logs.Logger.Critical("Server err:%v", err) } - +``` ## 发生错误发送邮件 上面的例子解释了如何设置发送邮件,我们通过如下的smtp配置用来发送邮件: +```html - +``` 邮件的格式通过criticalemail配置,然后通过其他的配置发送邮件服务器的配置,通过recipient配置接收邮件的用户,如果有多个用户可以再添加一行。 要测试这个代码是否正常工作,可以在代码中增加类似下面的一个假消息。不过记住过后要把它删除,否则上线之后就会收到很多垃圾邮件。 +```Go logs.Logger.Critical("test Critical message") - +``` 现在,只要我们的应用在线上记录一个Critical的信息,你的邮箱就会收到一个Email,这样一旦线上的系统出现问题,你就能立马通过邮件获知,就能及时的进行处理。 ## 使用应用日志 对于应用日志,每个人的应用场景可能会各不相同,有些人利用应用日志来做数据分析,有些人利用应用日志来做性能分析,有些人来做用户行为分析,还有些就是纯粹的记录,以方便应用出现问题的时候辅助查找问题。 举一个例子,我们需要跟踪用户尝试登陆系统的操作。这里会把成功与不成功的尝试都记录下来。记录成功的使用"Info"日志级别,而不成功的使用"warn"级别。如果想查找所有不成功的登陆,我们可以利用linux的grep之类的命令工具,如下: +```Go # cat /data/logs/roll.log | grep "failed login" 2012-12-11 11:12:00 WARN : failed login attempt from 11.22.33.44 username password - +``` 通过这种方式我们就可以很方便的查找相应的信息,这样有利于我们针对应用日志做一些统计和分析。另外我们还需要考虑日志的大小,对于一个高流量的Web应用来说,日志的增长是相当可怕的,所以我们在seelog的配置文件里面设置了logrotate,这样就能保证日志文件不会因为不断变大而导致我们的磁盘空间不够引起问题。 ## 小结 diff --git a/zh/12.2.md b/zh/12.2.md index 67ad1965..1c43f319 100644 --- a/zh/12.2.md +++ b/zh/12.2.md @@ -31,6 +31,8 @@ 通知用户在访问页面的时候我们可以有两种错误:404.html和error.html,下面分别显示了错误页面的源码: +```html + @@ -51,8 +53,11 @@ +``` 另一个源码: +```html + @@ -74,8 +79,11 @@ +``` 404的错误处理逻辑,如果是系统的错误也是类似的操作,同时我们看到在: +```Go + func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { sayhelloName(w, r) @@ -99,10 +107,12 @@ t.Execute(w, ErrorInfo) //执行模板的merger操作 } +``` ## 如何处理异常 我们知道在很多其他语言中有try...catch关键词,用来捕获异常情况,但是其实很多错误都是可以预期发生的,而不需要异常处理,应该当做错误来处理,这也是为什么Go语言采用了函数返回错误的设计,这些函数不会panic,例如如果一个文件找不到,os.Open返回一个错误,它不会panic;如果你向一个中断的网络连接写数据,net.Conn系列类型的Write函数返回一个错误,它们不会panic。这些状态在这样的程序里都是可以预期的。你知道这些操作可能会失败,因为设计者已经用返回错误清楚地表明了这一点。这就是上面所讲的可以预期发生的错误。 但是还有一种情况,有一些操作几乎不可能失败,而且在一些特定的情况下也没有办法返回错误,也无法继续执行,这样情况就应该panic。举个例子:如果一个程序计算x[j],但是j越界了,这部分代码就会导致panic,像这样的一个不可预期严重错误就会引起panic,在默认情况下它会杀掉进程,它允许一个正在运行这部分代码的goroutine从发生错误的panic中恢复运行,发生panic之后,这部分代码后面的函数和代码都不会继续执行,这是Go特意这样设计的,因为要区别于错误和异常,panic其实就是异常处理。如下代码,我们期望通过uid来获取User中的username信息,但是如果uid越界了就会抛出异常,这个时候如果我们没有recover机制,进程就会被杀死,从而导致程序不可服务。因此为了程序的健壮性,在一些地方需要建立recover机制。 +```Go func GetUser(uid int) (username string) { defer func() { @@ -114,7 +124,7 @@ username = User[uid] return } - +``` 上面介绍了错误和异常的区别,那么我们在开发程序的时候如何来设计呢?规则很简单:如果你定义的函数有可能失败,它就应该返回一个错误。当我调用其他package的函数时,如果这个函数实现的很好,我不需要担心它会panic,除非有真正的异常情况发生,即使那样也不应该是我去处理它。而panic和recover是针对自己开发package里面实现的逻辑,针对一些特殊情况来设计。 ## 小结 diff --git a/zh/12.3.md b/zh/12.3.md index 6e77e104..eed4fc10 100644 --- a/zh/12.3.md +++ b/zh/12.3.md @@ -7,6 +7,8 @@ - MarGo的一个实现思路,使用Commond来执行自身的应用,如果真想实现,那么推荐这种方案 +```Go + d := flag.Bool("d", false, "Whether or not to launch in the background(like a daemon)") if *d { cmd := exec.Command(os.Args[0], @@ -32,8 +34,10 @@ cmd.Process.Kill() } } - +``` + - 另一种是利用syscall的方案,但是这个方案并不完善: +```Go package main @@ -103,7 +107,8 @@ return 0 } - +``` + 上面提出了两种实现Go的daemon方案,但是我还是不推荐大家这样去实现,因为官方还没有正式的宣布支持daemon,当然第一种方案目前来看是比较可行的,而且目前开源库skynet也在采用这个方案做daemon。 ## Supervisord @@ -121,6 +126,8 @@ Supervisord可以通过`sudo easy_install supervisor`安装,当然也可以通 ### Supervisord配置 Supervisord默认的配置文件路径为/etc/supervisord.conf,通过文本编辑器修改这个文件,下面是一个示例的配置文件: +```conf + ;/etc/supervisord.conf [unix_http_server] file = /var/run/supervisord.sock @@ -161,6 +168,7 @@ Supervisord默认的配置文件路径为/etc/supervisord.conf,通过文本编 redirect_stderr = true stdout_logfile = /var/log/supervisord/blogdemon.log +``` ### Supervisord管理 Supervisord安装完成后有两个可用的命令行supervisor和supervisorctl,命令使用解释如下: diff --git a/zh/13.2.md b/zh/13.2.md index 4a75d31e..775b0b58 100644 --- a/zh/13.2.md +++ b/zh/13.2.md @@ -9,6 +9,7 @@ HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个st 路由器就是根据用户请求的事件信息转发到相应的处理函数(控制层)。 ## 默认的路由实现 在3.4小节有过介绍Go的http包的详解,里面介绍了Go的http包如何设计和实现路由,这里继续以一个例子来说明: +```Go func fooHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) @@ -21,7 +22,8 @@ HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个st }) log.Fatal(http.ListenAndServe(":8080", nil)) - + +``` 上面的例子调用了http默认的DefaultServeMux来添加路由,需要提供两个参数,第一个参数是希望用户访问此资源的URL路径(保存在r.URL.Path),第二参数是即将要执行的函数,以提供用户访问的资源。路由的思路主要集中在两点: - 添加路由信息 @@ -30,6 +32,7 @@ HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个st Go默认的路由添加是通过函数`http.Handle`和`http.HandleFunc`等来添加,底层都是调用了`DefaultServeMux.Handle(pattern string, handler Handler)`,这个函数会把路由信息存储在一个map信息中`map[string]muxEntry`,这就解决了上面说的第一点。 Go监听端口,然后接收到tcp连接会扔给Handler来处理,上面的例子默认nil即为`http.DefaultServeMux`,通过`DefaultServeMux.ServeHTTP`函数来进行调度,遍历之前存储的map路由信息,和用户访问的URL进行匹配,以查询对应注册的处理函数,这样就实现了上面所说的第二点。 +```Go for k, v := range mux.m { if !pathMatch(k, path) { @@ -40,7 +43,7 @@ Go监听端口,然后接收到tcp连接会扔给Handler来处理,上面的 h = v.h } } - +``` ## beego框架路由实现 目前几乎所有的Web应用路由实现都是基于http默认的路由器,但是Go自带的路由器有几个限制: @@ -55,6 +58,7 @@ beego框架的路由器基于上面的几点限制考虑设计了一种REST方 针对前面所说的限制点,我们首先要解决参数支持就需要用到正则,第二和第三点我们通过一种变通的方法来解决,REST的方法对应到struct的方法中去,然后路由到struct而不是函数,这样在转发路由的时候就可以根据method来执行不同的方法。 根据上面的思路,我们设计了两个数据类型controllerInfo(保存路径和对应的struct,这里是一个reflect.Type类型)和ControllerRegistor(routers是一个slice用来保存用户添加的路由信息,以及beego框架的应用信息) +```Go type controllerInfo struct { regex *regexp.Regexp @@ -67,12 +71,14 @@ beego框架的路由器基于上面的几点限制考虑设计了一种REST方 Application *App } - +``` ControllerRegistor对外的接口函数有 +```Go func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) - +``` 详细的实现如下所示: +```Go func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) { parts := strings.Split(pattern, "/") @@ -118,22 +124,25 @@ ControllerRegistor对外的接口函数有 p.routers = append(p.routers, route) } - +``` ### 静态路由实现 上面我们实现的动态路由的实现,Go的http包默认支持静态文件处理FileServer,由于我们实现了自定义的路由器,那么静态文件也需要自己设定,beego的静态文件夹路径保存在全局变量StaticDir中,StaticDir是一个map类型,实现如下: +```Go func (app *App) SetStaticPath(url string, path string) *App { StaticDir[url] = path return app } - +``` 应用中设置静态路径可以使用如下方式实现: +```Go beego.SetStaticPath("/img","/static/img") - +``` ### 转发路由 转发路由是基于ControllerRegistor里的路由信息来进行转发的,详细的实现如下代码所示: +```Go // AutoRoute func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -242,22 +251,25 @@ ControllerRegistor对外的接口函数有 http.NotFound(w, r) } } - +``` ### 使用入门 基于这样的路由设计之后就可以解决前面所说的三个限制点,使用的方式如下所示: 基本的使用注册路由: +```Go beego.BeeApp.RegisterController("/", &controllers.MainController{}) - +``` 参数注册: +```Go beego.BeeApp.RegisterController("/:param", &controllers.UserController{}) - +``` 正则匹配: +```Go beego.BeeApp.RegisterController("/users/:uid([0-9]+)", &controllers.UserController{}) - +``` ## links * [目录]() * 上一章: [项目规划](<13.1.md>) diff --git a/zh/13.3.md b/zh/13.3.md index 8a8e7aed..0ddb06c4 100644 --- a/zh/13.3.md +++ b/zh/13.3.md @@ -8,6 +8,7 @@ MVC设计模式是目前Web应用开发中最常见的架构模式,通过分 ## beego的REST设计 前面小节介绍了路由实现了注册struct的功能,而struct中实现了REST方式,因此我们需要设计一个用于逻辑处理controller的基类,这里主要设计了两个类型,一个struct、一个interface +```Go type Controller struct { Ct *Context @@ -32,8 +33,9 @@ MVC设计模式是目前Web应用开发中最常见的架构模式,通过分 Finish() //执行完成之后的处理 Render() error //执行完method对应的方法之后渲染页面 } - +``` 那么前面介绍的路由add函数的时候是定义了ControllerInterface类型,因此,只要我们实现这个接口就可以,所以我们的基类Controller实现如下的方法: +```Go func (c *Controller) Init(ct *Context, cn string) { c.Data = make(map[interface{}]interface{}) @@ -113,17 +115,19 @@ MVC设计模式是目前Web应用开发中最常见的架构模式,通过分 func (c *Controller) Redirect(url string, code int) { c.Ct.Redirect(code, url) } - +``` 上面的controller基类已经实现了接口定义的函数,通过路由根据url执行相应的controller的原则,会依次执行如下: +```Go Init() 初始化 Prepare() 执行之前的初始化,每个继承的子类可以来实现该函数 method() 根据不同的method执行不同的函数:GET、POST、PUT、HEAD等,子类来实现这些函数,如果没实现,那么默认都是403 Render() 可选,根据全局变量AutoRender来判断是否执行 Finish() 执行完之后执行的操作,每个继承的子类可以来实现该函数 - +``` ## 应用指南 上面beego框架中完成了controller基类的设计,那么我们在我们的应用中可以这样来设计我们的方法: +```Go package controllers @@ -140,12 +144,13 @@ MVC设计模式是目前Web应用开发中最常见的架构模式,通过分 this.Data["Email"] = "astaxie@gmail.com" this.TplNames = "index.tpl" } - +``` 上面的方式我们实现了子类MainController,实现了Get方法,那么如果用户通过其他的方式(POST/HEAD等)来访问该资源都将返回403,而如果是Get来访问,因为我们设置了AutoRender=true,那么在执行完Get方法之后会自动执行Render函数,就会显示如下界面: ![](images/13.4.beego.png?raw=true) index.tpl的代码如下所示,我们可以看到数据的设置和显示都是相当的简单方便: +```html @@ -156,7 +161,8 @@ index.tpl的代码如下所示,我们可以看到数据的设置和显示都

Hello, world!{{.Username}},{{.Email}}

- + +``` ## links * [目录]() diff --git a/zh/13.4.md b/zh/13.4.md index 07b3d92c..bce40ce8 100644 --- a/zh/13.4.md +++ b/zh/13.4.md @@ -8,6 +8,7 @@ ## beego的日志设计 beego的日志设计部署思路来自于seelog,根据不同的level来记录日志,但是beego设计的日志系统比较轻量级,采用了系统的log.Logger接口,默认输出到os.Stdout,用户可以实现这个接口然后通过beego.SetLogger设置自定义的输出,详细的实现如下所示: +```Go // Log levels to control the logging output. const ( @@ -33,9 +34,10 @@ beego的日志设计部署思路来自于seelog,根据不同的level来记录 func SetLevel(l int) { level = l } - +``` 上面这一段实现了日志系统的日志分级,默认的级别是Trace,用户通过SetLevel可以设置不同的分级。 - +```Go + // logger references the used application logger. var BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime) @@ -85,7 +87,7 @@ beego的日志设计部署思路来自于seelog,根据不同的level来记录 BeeLogger.Printf("[C] %v\n", v) } } - +``` 上面这一段代码默认初始化了一个BeeLogger对象,默认输出到os.Stdout,用户可以通过beego.SetLogger来设置实现了logger的接口输出。这里面实现了六个函数: - Trace(一般的记录信息,举例如下:) @@ -117,6 +119,7 @@ beego的日志设计部署思路来自于seelog,根据不同的level来记录 配置信息的解析,beego实现了一个key=value的配置文件读取,类似ini配置文件的格式,就是一个文件解析的过程,然后把解析的数据保存到map中,最后在调用的时候通过几个string、int之类的函数调用返回相应的值,具体的实现请看下面: 首先定义了一些ini配置文件的一些全局性常量 : +```Go var ( bComment = []byte{'#'} @@ -124,9 +127,10 @@ beego的日志设计部署思路来自于seelog,根据不同的level来记录 bEqual = []byte{'='} bDQuote = []byte{'"'} ) - +``` 定义了配置文件的格式: - +```Go + // A Config represents the configuration. type Config struct { filename string @@ -135,9 +139,10 @@ beego的日志设计部署思路来自于seelog,根据不同的level来记录 offset map[string]int64 // key: offset; for editing. sync.RWMutex } - +``` 定义了解析文件的函数,解析文件的过程是打开文件,然后一行一行的读取,解析注释、空行和key=value数据: - +```Go + // ParseFile creates a new Config and parses the file configuration from the // named file. func LoadConfig(name string) (*Config, error) { @@ -196,9 +201,10 @@ beego的日志设计部署思路来自于seelog,根据不同的level来记录 } return cfg, nil } - +``` 下面实现了一些读取配置文件的函数,返回的值确定为bool、int、float64或string: - +```Go + // Bool returns the boolean value for a given key. func (c *Config) Bool(key string) (bool, error) { return strconv.ParseBool(c.data[key]) @@ -218,9 +224,10 @@ beego的日志设计部署思路来自于seelog,根据不同的level来记录 func (c *Config) String(key string) string { return c.data[key] } - +``` ## 应用指南 下面这个函数是我一个应用中的例子,用来获取远程url地址的json数据,实现如下: +```Go func GetJson() { resp, err := http.Get(beego.AppConfig.String("url")) @@ -235,13 +242,14 @@ beego的日志设计部署思路来自于seelog,根据不同的level来记录 beego.Critical("error:", err) } } - +``` 函数中调用了框架的日志函数`beego.Critical`函数用来报错,调用了`beego.AppConfig.String("url")`用来获取配置文件中的信息,配置文件的信息如下(app.conf): +```Go appname = hs url ="http://www.api.com/api.html" - +``` ## links * [目录]() * 上一章: [controller设计](<13.3.md>) diff --git a/zh/13.5.md b/zh/13.5.md index 83888f39..9a64f130 100644 --- a/zh/13.5.md +++ b/zh/13.5.md @@ -24,6 +24,7 @@ ## 博客路由 博客主要的路由规则如下所示: +```Go //显示博客首页 beego.Router("/", &controllers.IndexController{}) @@ -36,9 +37,10 @@ //编辑博文 beego.Router("/edit/:id([0-9]+)", &controllers.EditController{}) - +``` ## 数据库结构 数据库设计最简单的博客信息 +```sql CREATE TABLE entries ( id INT AUTO_INCREMENT, @@ -47,10 +49,12 @@ created DATETIME, primary key (id) ); - +``` ## 控制器 IndexController: +```Go + type IndexController struct { beego.Controller } @@ -60,9 +64,11 @@ IndexController: this.Layout = "layout.tpl" this.TplNames = "index.tpl" } - +``` ViewController: +```Go + type ViewController struct { beego.Controller } @@ -73,8 +79,9 @@ ViewController: this.Layout = "layout.tpl" this.TplNames = "view.tpl" } - +``` NewController +```Go type NewController struct { beego.Controller @@ -94,8 +101,9 @@ NewController models.SaveBlog(blog) this.Ctx.Redirect(302, "/") } - +``` EditController +```Go type EditController struct { beego.Controller @@ -118,8 +126,9 @@ EditController models.SaveBlog(blog) this.Ctx.Redirect(302, "/") } - +``` DeleteController +```Go type DeleteController struct { beego.Controller @@ -132,8 +141,9 @@ DeleteController models.DelBlog(blog) this.Ctx.Redirect(302, "/") } - +``` ## model层 +```Go package models @@ -183,11 +193,12 @@ DeleteController db.Delete(&blog) return } - +``` ## view层 layout.tpl +```html @@ -210,8 +221,10 @@ layout.tpl +``` index.tpl +```html

Blog posts

@@ -225,15 +238,17 @@ index.tpl {{end}} - +``` view.tpl +```html

{{.Post.Title}}

{{.Post.Created}}
{{.Post.Content}} - +``` new.tpl +```html

New Blog Post

@@ -241,8 +256,9 @@ new.tpl 内容:
- +``` edit.tpl +```html

Edit {{.Post.Title}}

@@ -253,7 +269,7 @@ edit.tpl - +``` ## links * [目录]() * 上一章: [日志和配置设计](<13.4.md>) diff --git a/zh/14.1.md b/zh/14.1.md index 9dfc2e0e..91587ec6 100644 --- a/zh/14.1.md +++ b/zh/14.1.md @@ -3,6 +3,7 @@ ## beego静态文件实现和设置 Go的net/http包中提供了静态文件的服务,`ServeFile`和`FileServer`等函数。beego的静态文件处理就是基于这一层处理的,具体的实现如下所示: +```Go //static file server for prefix, staticDir := range StaticDir { @@ -13,13 +14,14 @@ Go的net/http包中提供了静态文件的服务,`ServeFile`和`FileServer` return } } - +``` StaticDir里面保存的是相应的url对应到静态文件所在的目录,因此在处理URL请求的时候只需要判断对应的请求地址是否包含静态处理开头的url,如果包含的话就采用http.ServeFile提供服务。 举例如下: +```Go beego.StaticDir["/asset"] = "/static" - +``` 那么请求url如`http://www.beego.me/asset/bootstrap.css`就会请求`/static/bootstrap.css`来提供反馈给客户端。 ## bootstrap集成 @@ -47,11 +49,14 @@ Bootstrap是Twitter推出的一个开源的用于前端开发的工具包。对 图14.2 项目中静态文件目录结构 2. 因为beego默认设置了StaticDir的值,所以如果你的静态文件目录是static的话就无须再增加了: +```Go StaticDir["/static"] = "static" - +``` 3. 模板中使用如下的地址就可以了: +```html + //css文件 @@ -60,7 +65,7 @@ Bootstrap是Twitter推出的一个开源的用于前端开发的工具包。对 //图片文件 - +``` 上面可以实现把bootstrap集成到beego中来,如下展示的图就是集成进来之后的展现效果图: ![](images/14.1.bootstrap3.png?raw=true) diff --git a/zh/14.2.md b/zh/14.2.md index e6072ef2..5294244a 100644 --- a/zh/14.2.md +++ b/zh/14.2.md @@ -3,6 +3,7 @@ ## session集成 beego中主要有以下的全局变量来控制session处理: +```Go //related to session SessionOn bool // 是否开启session模块,默认不开启 @@ -11,8 +12,9 @@ beego中主要有以下的全局变量来控制session处理: SessionGCMaxLifetime int64 // cookies有效期 GlobalSessions *session.Manager //全局session控制器 - +``` 当然上面这些变量需要初始化值,也可以按照下面的代码来配合配置文件以设置这些值: +```Go if ar, err := AppConfig.Bool("sessionon"); err != nil { SessionOn = false @@ -35,32 +37,36 @@ beego中主要有以下的全局变量来控制session处理: } else { SessionGCMaxLifetime = 3600 } - +``` 在beego.Run函数中增加如下代码: +```Go if SessionOn { GlobalSessions, _ = session.NewManager(SessionProvider, SessionName, SessionGCMaxLifetime) go GlobalSessions.GC() } - +``` 这样只要SessionOn设置为true,那么就会默认开启session功能,独立开一个goroutine来处理session。 为了方便我们在自定义Controller中快速使用session,作者在`beego.Controller`中提供了如下方法: +```Go func (c *Controller) StartSession() (sess session.Session) { sess = GlobalSessions.SessionStart(c.Ctx.ResponseWriter, c.Ctx.Request) return } - +``` ## session使用 通过上面的代码我们可以看到,beego框架简单地继承了session功能,那么在项目中如何使用呢? 首先我们需要在应用的main入口处开启session: +```Go beego.SessionOn = true - +``` 然后我们就可以在控制器的相应方法中如下所示的使用session了: +```Go func (this *MainController) Get() { var intcount int @@ -78,22 +84,27 @@ beego中主要有以下的全局变量来控制session处理: this.Data["Count"] = intcount this.TplNames = "index.tpl" } - +``` 上面的代码展示了如何在控制逻辑中使用session,主要分两个步骤: 1. 获取session对象 - + +```Go + //获取对象,类似PHP中的session_start() sess := this.StartSession() +``` 2. 使用session进行一般的session值操作 - + +```Go + //获取session值,类似PHP中的$_SESSION["count"] sess.Get("count") //设置session值 sess.Set("count", intcount) - +``` 从上面代码可以看出基于beego框架开发的应用中使用session相当方便,基本上和PHP中调用`session_start()`类似。 diff --git a/zh/14.3.md b/zh/14.3.md index bf9bf2e1..600f0bd7 100644 --- a/zh/14.3.md +++ b/zh/14.3.md @@ -21,6 +21,7 @@ 对于开发者来说,一般开发过程都是相当复杂,而且大多是在重复一样的工作。假设一个场景项目中忽然需要增加一个表单数据,那么局部代码的整个流程都需要修改。我们知道Go里面struct是常用的一个数据结构,因此beego的form采用了struct来处理表单信息。 首先定义一个开发Web应用时相对应的struct,一个字段对应一个form元素,通过struct的tag来定义相应的元素信息和验证信息,如下所示: +```Go type User struct{ Username string `form:text,valid:required` @@ -29,23 +30,26 @@ Email string `form:text,valid:required|valid_email` Introduce string `form:textarea` } - +``` 定义好struct之后接下来在controller中这样操作 +```Go func (this *AddController) Get() { this.Data["form"] = beego.Form(&User{}) this.Layout = "admin/layout.html" this.TplNames = "admin/add.tpl" } - +``` 在模板中这样显示表单 +```html

New Blog Post

{{.form.render()}}
- +``` 上面我们定义好了整个的第一步,从struct到显示表单的过程,接下来就是用户填写信息,服务器端接收数据然后验证,最后插入数据库。 +```Go func (this *AddController) Post() { var user User @@ -56,7 +60,7 @@ models.UserInsert(&user) this.Ctx.Redirect(302, "/admin/index") } - +``` ## 表单类型 以下列表列出来了对应的form元素信息: diff --git a/zh/14.4.md b/zh/14.4.md index 79cbb1bf..efa43c16 100644 --- a/zh/14.4.md +++ b/zh/14.4.md @@ -9,10 +9,12 @@ beego目前没有针对这三种方式进行任何形式的集成,但是可以 ## HTTP Basic和 HTTP Digest认证 这两个认证是一些应用采用的比较简单的认证,目前已经有开源的第三方库支持这两个认证: - - github.com/abbot/go-http-auth +```Go + github.com/abbot/go-http-auth +``` 下面代码演示了如何把这个库引入beego中从而实现认证: +```Go package controllers @@ -45,22 +47,25 @@ beego目前没有针对这三种方式进行任何形式的集成,但是可以 this.Data["Email"] = "astaxie@gmail.com" this.TplNames = "index.tpl" } - +``` 上面代码利用了beego的prepare函数,在执行正常逻辑之前调用了认证函数,这样就非常简单的实现了http auth,digest的认证也是同样的原理。 ## oauth和oauth2的认证 oauth和oauth2是目前比较流行的两种认证方式,还好第三方有一个库实现了这个认证,但是是国外实现的,并没有QQ、微博之类的国内应用认证集成: +```Go github.com/bradrydzewski/go.auth - +``` 下面代码演示了如何把该库引入beego中从而实现oauth的认证,这里以github为例演示: 1. 添加两条路由 +```Go beego.RegisterController("/auth/login", &controllers.GithubController{}) beego.RegisterController("/mainpage", &controllers.PageController{}) - +``` 2. 然后我们处理GithubController登陆的页面: +```Go package controllers @@ -89,8 +94,9 @@ oauth和oauth2是目前比较流行的两种认证方式,还好第三方有一 githubHandler.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request) } - +``` 3. 处理登陆成功之后的页面 +```Go package controllers @@ -126,7 +132,7 @@ oauth和oauth2是目前比较流行的两种认证方式,还好第三方有一 this.Data["name"] = user.Name() this.TplNames = "home.tpl" } - +``` 整个的流程如下,首先打开浏览器输入地址: ![](images/14.4.github.png?raw=true) @@ -147,7 +153,7 @@ oauth和oauth2是目前比较流行的两种认证方式,还好第三方有一 ## 自定义认证 自定义的认证一般都是和session结合验证的,如下代码来源于一个基于beego的开源博客: - +```Go //登陆处理 func (this *LoginController) Post() { @@ -239,8 +245,9 @@ oauth和oauth2是目前比较流行的两种认证方式,还好第三方有一 } return true } - +``` 有了用户登陆和注册之后,其他模块的地方可以增加如下这样的用户是否登陆的判断: +```Go func (this *AddBlogController) Prepare() { sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) @@ -252,7 +259,7 @@ oauth和oauth2是目前比较流行的两种认证方式,还好第三方有一 } this.Data["Username"] = sess_username } - +``` ## links * [目录]() * 上一节: [表单及验证支持](<14.3.md>) diff --git a/zh/14.5.md b/zh/14.5.md index e37cfe8d..2cd1e11e 100644 --- a/zh/14.5.md +++ b/zh/14.5.md @@ -4,20 +4,23 @@ ## i18n集成 beego中设置全局变量如下: +```Go Translation i18n.IL Lang string //设置语言包,zh、en LangPath string //设置语言包所在位置 - +``` 初始化多语言函数: +```Go func InitLang(){ beego.Translation:=i18n.NewLocale() beego.Translation.LoadPath(beego.LangPath) beego.Translation.SetLocale(beego.Lang) } - +``` 为了方便在模板中直接调用多语言包,我们设计了三个函数来处理响应的多语言: +```Go beegoTplFuncMap["Trans"] = i18n.I18nT beegoTplFuncMap["TransDate"] = i18n.I18nTimeDate @@ -58,17 +61,19 @@ beego中设置全局变量如下: } return beego.Translation.Money(s) } - +``` ## 多语言开发使用 1. 设置语言以及语言包所在位置,然后初始化i18n对象: +```Go beego.Lang = "zh" beego.LangPath = "views/lang" beego.InitLang() - +``` 2. 设计多语言包 上面讲了如何初始化多语言包,现在设计多语言包,多语言包是json文件,如第十章介绍的一样,我们需要把设计的文件放在LangPath下面,例如zh.json或者en.json +```json # zh.json @@ -87,17 +92,19 @@ beego中设置全局变量如下: "create": "Create" } } - +``` 3. 使用语言包 我们可以在controller中调用翻译获取响应的翻译语言,如下所示: +```Go func (this *MainController) Get() { this.Data["create"] = beego.Translation.Translate("create") this.TplNames = "index.tpl" } - +``` 我们也可以在模板中直接调用响应的翻译函数: +```Go //直接文本翻译 {{.create | Trans}} @@ -107,7 +114,7 @@ beego中设置全局变量如下: //货币翻译 {{.money | TransMoney}} - +``` ## links * [目录]() * 上一节: [用户认证](<14.4.md>) diff --git a/zh/14.6.md b/zh/14.6.md index a5a28500..53843f4d 100644 --- a/zh/14.6.md +++ b/zh/14.6.md @@ -1,23 +1,26 @@ # 14.6 pprof支持 Go语言有一个非常棒的设计就是标准库里面带有代码的性能监控工具,在两个地方有包: +```Go net/http/pprof runtime/pprof - +``` 其实net/http/pprof中只是使用runtime/pprof包来进行封装了一下,并在http端口上暴露出来 ## beego支持pprof 目前beego框架新增了pprof,该特性默认是不开启的,如果你需要测试性能,查看相应的执行goroutine之类的信息,其实Go的默认包"net/http/pprof"已经具有该功能,如果按照Go默认的方式执行Web,默认就可以使用,但是由于beego重新封装了ServHTTP函数,默认的包是无法开启该功能的,所以需要对beego的内部改造支持pprof。 - 首先在beego.Run函数中根据变量是否自动加载性能包 +```Go if PprofOn { BeeApp.RegisterController(`/debug/pprof`, &ProfController{}) BeeApp.RegisterController(`/debug/pprof/:pp([\w]+)`, &ProfController{}) } - +``` - 设计ProfConterller +```Go package beego @@ -45,13 +48,14 @@ Go语言有一个非常棒的设计就是标准库里面带有代码的性能监 this.Ctx.ResponseWriter.WriteHeader(200) } - +``` ## 使用入门 通过上面的设计,你可以通过如下代码开启pprof: +```Go beego.PprofOn = true - +``` 然后你就可以在浏览器中打开如下URL就看到如下界面: ![](images/14.6.pprof.png?raw=true) @@ -64,9 +68,10 @@ Go语言有一个非常棒的设计就是标准库里面带有代码的性能监 图14.8 显示当前goroutine的详细信息 我们还可以通过命令行获取更多详细的信息 +```Go go tool pprof http://localhost:8080/debug/pprof/profile - +``` 这时候程序就会进入30秒的profile收集时间,在这段时间内拼命刷新浏览器上的页面,尽量让cpu占用性能产生数据。 (pprof) top10