Gin은 간단하고 빠른 golang
프레임워크입니다. 이 기사에서는 주로 gin
의 라우팅 구성과 사용(주로 post 방법)을 소개합니다.
골랭 >= 1.18
# development
go run main.go
# run development
# https://github.com/cosmtrek/air Live reload for Go apps
air
# build
go build
# or
make build
# run production
# export GIN_MODE=release
./gin-router-web
# server 8080
http://localhost:8080/
# file chunk upload
http://localhost:8080/upload_chunks
# docker deploy
make serve
# #
go mod tidy
[ POST ] /api/form_post
[ POST ] /api/json_post
[ POST ] /api/urlencoded_post
[ POST ] /api/json_and_form_post
[ POST ] /api/xml_post
[ POST ] /api/file_upload
[ POST ] /api/file_chunk_upload
[ GET ] /api/query
func setStaticFS ( r * gin. Engine ) {
// set html template
r . LoadHTMLGlob ( "views/*" )
// set server static
r . StaticFile ( "favicon.ico" , "./public/favicon.ico" )
r . StaticFS ( "/static" , http . Dir ( "public/static" ))
r . StaticFS ( "/upload" , http . Dir ( "upload" ))
}
func (engine *Engine) LoadHTMLGlob(pattern string)
함수는 전역 패턴의 HTML 파일 식별자를 로드하고 결과를 HTML 렌더러와 연결합니다.
func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes
상대 경로 정적 리소스를 설정합니다.
API 라우팅 그룹
api := r . Group ( "/api" )
{
api . POST ( "/form_post" , formPost )
api . POST ( "/json_post" , jsonPost )
api . POST ( "/urlencoded_post" , urlencodedPost )
api . POST ( "/json_and_form_post" , jsonAndFormPost )
api . POST ( "/xml_post" , xmlPost )
api . POST ( "/file_upload" , fileUpload )
api . GET ( "/list" , func ( c * gin. Context ) {
name := c . Query ( "name" )
message := c . Query ( "message" )
nick := c . DefaultQuery ( "nick" , "anonymous" )
c . JSON ( http . StatusOK , helper . BuildResponse (gin. H {
"name" : name ,
"message" : message ,
"nick" : nick ,
}))
})
}
요청 Headers
에 일반적으로 사용되는 Content-Type
유형에는 text/plain
, text/html
, application/json
, application/x-www-form-urlencoded
, application/xml
및 multipart/form-data
등이 포함됩니다.
text/plain
일반 텍스트text/html
HTML 문서application/json
json 형식 데이터application/x-www-form-urlencoded
양식application/xml
xml 형식 데이터application/form-data
주로 파일을 업로드하는 데 사용됩니다.몸짓 광대극
진 라우팅 구현
// User user struct
type User struct {
Name string `json:"name" form:"name" xml:"name"`
Message string `json:"message" form:"message" xml:"message"`
Nick string `json:"nick" form:"nick" xml:"nick"`
}
// FormPost 表单提交
func FormPost ( c * gin. Context ) {
message := c . PostForm ( "message" )
nick := c . DefaultPostForm ( "nick" , "default nick" )
name := c . DefaultPostForm ( "name" , "default name" )
user := User {
Name : name ,
Nick : nick ,
Message : message ,
}
// This way is better
// 下面这种方式 会自动和定义的结构体进行绑定
// user := &User{}
// c.ShouldBind(user)
c . JSON ( http . StatusOK , helper . BuildResponse ( user ))
}
HTML 구현
< form method =" post " action =" /api/form_post " id =" form " >
< div class =" form-item " >
< label for =" name " > name </ label >
< input type =" text " id =" name " name =" name " />
</ div >
< div class =" form-item " >
< label for =" message " > message </ label >
< input type =" text " id =" message " name =" message " />
</ div >
< div class =" form-item " >
< label for =" name " > nick </ label >
< input type =" text " id =" nick " name =" nick " />
</ div >
< button type =" submit " >提交</ button >
</ form >
application/json
유형 데이터를 제출합니다.진 라우팅 구현
// JSONPost json
func JSONPost ( c * gin. Context ) {
var user User
if err := c . BindJSON ( & user ); err != nil {
c . AbortWithStatusJSON ( http . StatusOK , helper . BuildErrorResponse ( http . StatusBadRequest , "invalid parameter" ))
return
}
c . JSON ( http . StatusOK , helper . BuildResponse ( user ))
}
JS 구현
axios ( {
method : "post" ,
url : "/api/json_post" ,
headers : {
"Content-Type" : "application/json" ,
} ,
data ,
} ) . then ( ( res ) => {
console . log ( res . data ) ;
$ ( ".json-msg" ) . text ( `success ${ new Date ( ) } ` ) ;
} ) ;
application/x-www-form-urlencoded
유형 데이터 제출진 구현
// UrlencodedPost application/x-www-form-urlencoded
func UrlencodedPost ( c * gin. Context ) {
limit := c . Query ( "limit" )
name := c . PostForm ( "name" )
message := c . PostForm ( "message" )
nick := c . DefaultPostForm ( "nick" , "1231412" )
user := User {
Name : name ,
Nick : nick ,
Message : message ,
}
// This way is better
// 下面这种方式 会自动和定义的结构体进行绑定
// user := &User{}
// c.ShouldBind(user)
log . Printf ( "request query limit: %s n " , limit )
c . JSON ( http . StatusOK , helper . BuildResponse ( user ))
}
JS 구현
axios ( {
method : "post" ,
url : "/api/urlencoded_post?name=shineshao" ,
headers : {
"Content-Type" : "application/x-www-form-urlencoded" ,
} ,
data : $ . param ( data ) ,
} ) . then ( ( res ) => {
console . log ( res . data ) ;
$ ( ".urlencoded-msg" ) . text ( `success ${ new Date ( ) } ` ) ;
} ) ;
application/x-www-form-urlencoded
또는 application/json
유형 데이터를 제출합니다.진
//JSONAndFormPost application/json application/x-www-form-urlencoded
func JSONAndFormPost ( c * gin. Context ) {
var user User
if err := c . ShouldBind ( & user ); err != nil {
c . AbortWithStatusJSON ( http . StatusOK , helper . BuildErrorResponse ( http . StatusBadRequest , "invalid parameter" ))
return
}
c . JSON ( http . StatusOK , helper . BuildResponse ( user ))
}
JS 구현
// json
axios ( {
method : "post" ,
url : "/api/json_and_form_post" ,
headers : {
"Content-Type" : "application/json" ,
} ,
data ,
} ) . then ( ( res ) => {
console . log ( res . data ) ;
$ ( ".jsonandform-msg" ) . text ( `success application/json data, ${ new Date ( ) } ` ) ;
} ) ;
// x-www-form-urlencoded
axios ( {
method : "post" ,
url : "/api/json_and_form_post" ,
headers : {
"Content-Type" : "application/x-www-form-urlencoded" ,
} ,
data : $ . param ( data ) ,
} ) . then ( ( res ) => {
console . log ( res . data ) ;
$ ( ".jsonandform-msg" ) . text (
`success application/x-www-form-urlencoded data ${ new Date ( ) } `
) ;
} ) ;
application/xml
유형 데이터( application/xml
)를 제출합니다.진 구현
//XMLPost xml
func XMLPost ( c * gin. Context ) {
var user User
// c.ShouldBind(&user)
// c.Bind(&user)
if err := c . BindXML ( & user ); err != nil {
c . AbortWithStatusJSON ( http . StatusOK , helper . BuildErrorResponse ( http . StatusBadRequest , "invalid parameter" ))
return
}
c . JSON ( http . StatusOK , helper . BuildResponse ( user ))
}
JS 구현
axios ( {
method : "post" ,
url : "/api/xml_post" ,
headers : {
"Content-Type" : "application/xml" ,
} ,
data : `<xml><name> ${ data . name } </name><message> ${ data . message } </message><nick> ${ data . nick } </nick></xml>` ,
} ) ;
multipart/form-data
유형 데이터( multipart/form-data
)를 제출합니다.gin은 파일 업로드(api/upload.go)를 구현합니다.
func fileUpload ( c * gin. Context ) {
filesUrl := make ([] string , 0 )
form , err := c . MultipartForm ()
if err != nil {
log . Println ( "postMultipleFile error: %s" )
}
files := form . File [ "file" ]
_ , err = os . Stat ( "upload" )
if err != nil {
os . Mkdir ( "upload" , os . ModePerm )
}
for _ , file := range files {
log . Println ( file . Filename )
// Upload the file to specific dst.
if err = c . SaveUploadedFile ( file , "upload/" + file . Filename ); err != nil {
log . Println ( "SaveUploadedFile error: %s" )
return
}
filesUrl = append ( filesUrl , "upload/" + file . Filename )
}
c . JSON ( http . StatusOK , models . BuildResponse (gin. H {
"urls" : filesURL ,
}))
}
HTML 구현
< div >
< form id =" multipleForm " >
< input
type =" file "
name =" file "
id =" file "
multiple =" multiple "
accept =" image/* "
/>
</ form >
< button class =" file_upload " >开始上传文件</ button >
</ div >
JS 구현
// 单个文件上传
// var fd = new FormData()
// var file = document.getElementById('file')
// fd.append('file', file.files[0])
axios ( {
method : "post" ,
url : "/api/file_upload" ,
headers : {
"Content-Type" : "application/form-data" ,
} ,
// data:fd // 单个文件上传
data : new FormData ( $ ( "#multipleForm" ) [ 0 ] ) ,
} ) . then ( ( res ) => {
console . log ( res . data ) ;
const urls = res . data . data . urls || [ ] ;
let imgHtml = "" ;
for ( let i = 0 ; i < urls . length ; i ++ ) {
imgHtml += `<div><img style="width: 200px" src="/ ${ urls [ i ] } " /> <div>/ ${ urls [ i ] } </div></div>` ;
}
$ ( ".file_upload-msg" ) . html (
`<div> ${ new Date ( ) } <div>
${ imgHtml }
</div>
</div>`
) ;
} ) ;
공식 파일 업로드 데모
클라이언트는 파일 크기와 사용자가 조각화하려는 크기를 기반으로 파일 조각 수를 계산합니다. 클라이언트는 파일의 모든 조각을 서버에 업로드하기 위해 인터페이스를 하나씩 요청합니다.
서버는 클라이언트가 업로드한 파일 조각을 수락하고 이를 캐시하거나 파일을 생성하고 마지막 조각이 성공적으로 업로드될 때까지 조각을 읽습니다.
http://localhost:8080/upload_chunks
서버 측은 go 언어의 gin 프레임워크를 사용합니다.
type ChunkFile struct {
Name string `json:"name" form:"name"`
Chunk int `json:"chunk" form:"chunk"`
Chunks int `json:"chunks" form:"chunks"`
}
func PathExists ( path string ) ( bool , error ) {
_ , err := os . Stat ( path )
if err == nil {
return true , nil
}
if os . IsNotExist ( err ) {
return false , nil
}
return false , err
}
// 文件分片上传handler
func fileChunkUpload ( c * gin. Context ) {
var chunkFile ChunkFile
r := c . Request
c . Bind ( & chunkFile )
var Buf = make ([] byte , 0 )
// in your case file would be fileupload
file , _ , _ := r . FormFile ( "file" )
log . Println ( "this is " , chunkFile . File )
Buf , _ = ioutil . ReadAll ( file )
filePath := "upload/" + chunkFile . Name
fd , _ := os . OpenFile ( filePath , os . O_RDWR | os . O_CREATE | os . O_APPEND , 0644 )
fd . Write ( Buf )
fd . Close ()
if chunkFile . Chunk + 1 == chunkFile . Chunks {
c . JSON ( http . StatusOK , gin. H {
"state" : "SUCCESS" ,
"url" : "/" + filePath ,
})
} else {
contentType := strings . Split ( c . GetHeader ( "Content-Type" ), "boundary=" )
c . String ( http . StatusOK , contentType [ 1 ])
}
}
서버 인터페이스의 전체 코드
클라이언트는 plupload 파일 업로드 플러그인을 사용합니다. 장점은 객체를 생성할 때 chunk_size
속성을 구성하여 달성할 수 있는 다중 부분 업로드를 제공한다는 것입니다(플러그인의 하위 계층에서 조각 수를 계산합니다). 파일 크기와 chunk_size
기준으로 합니다.
var uploader = new plupload . Uploader ( {
runtimes : "html5,flash,silverlight,html4" ,
browse_button : "pickfiles" , // you can pass an id...
container : document . getElementById ( "container" ) , // ... or DOM Element itself
url : "/api/file_chunk_upload" ,
flash_swf_url : "/static/js/Moxie.swf" ,
silverlight_xap_url : "/static/js/Moxie.xap" ,
chunk_size : "100kb" ,
filters : {
max_file_size : "10mb" ,
mime_types : [
{ title : "Image files" , extensions : "jpg,gif,png,jpeg" } ,
{ title : "Zip files" , extensions : "zip" } ,
] ,
} ,
init : {
PostInit : function ( ) {
document . getElementById ( "filelist" ) . innerHTML = "" ;
document . getElementById ( "uploadfiles" ) . onclick = function ( ) {
uploader . start ( ) ;
return false ;
} ;
} ,
FilesAdded : function ( up , files ) {
plupload . each ( files , function ( file ) {
document . getElementById ( "filelist" ) . innerHTML +=
'<div id="' +
file . id +
'">' +
file . name +
" (" +
plupload . formatSize ( file . size ) +
") <b></b></div>" ;
} ) ;
} ,
UploadProgress : function ( up , file ) {
document . getElementById ( file . id ) . getElementsByTagName ( "b" ) [ 0 ] . innerHTML =
"<span>" + file . percent + "%</span>" ;
} ,
Error : function ( up , err ) {
document
. getElementById ( "console" )
. appendChild (
document . createTextNode ( "nError #" + err . code + ": " + err . message )
) ;
} ,
} ,
} ) ;
uploader . bind ( "ChunkUploaded" , function ( up , file , info ) {
// do some chunk related stuff
console . log ( info ) ;
} ) ;
uploader . init ( ) ;
클라이언트 파일 업로드 전체 코드
데모