Golang grpc-gateway에서 RESTful API 만들기
15686 단어 gRPCgrpc-gatewayRESTfulREST-API5
소개
"gRPC를 사용해 보았지만 REST-API를 사용할 수 없습니까?"
"gRPC 외에도 REST-API를 사용하고 싶습니다."
"gRPC로 만든 것을 쉽게 검증하고 싶다"
라고 생각해 조사하면grpc-gateway
라는 것이 있었으므로, 사용해 보겠습니다.
htps : // 기주 b. m / grpc 에코 sys m / grpc가 주름 y
gRPC
gRPC에 대해서는 별도 기사를 작성하고 있으므로 여기를 참조하십시오.
htps : // m / 류 3 / ms / d5 ~ c28 a 412 a 06 e 17 e f98
grpc-gateway 개요
grpc-gateway는 gRPC로 작성된 API를 JSON over HTTP API로 변환합니다. 코드 생성기 역할을 하며 특정 역방향 프록시 서버를 생성합니다. 아래 그림을 참조하십시오.
설치
아래 명령으로 설치하십시오.
$ go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
$ go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
$ go get -u -v github.com/golang/protobuf/protoc-gen-go
사용해 보자.
1.gRPC 서비스 정의
proto/service.protosyntax = "proto3";
package example;
import "google/api/annotations.proto";
service HelloWorldService {
rpc SayHello (HelloRequest) returns (HelloReply) {
}
rpc GetUser(GetUserRequest) returns (User) {
}
rpc CreateUser(CreateUserRequest) returns (User) {
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
message GetUserRequest {
string id = 1;
}
message CreateUserRequest {
string name = 1;
}
message User {
string id = 1;
string name = 2;
}
2. google.api.http를 추가합니다.
service.protosyntax = "proto3";
package example;
import "google/api/annotations.proto";
service HelloWorldService {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
get: "/v1/example/sayhello/{name}"
};
}
rpc GetUser(GetUserRequest) returns (User) {
option (google.api.http) = {
get: "/v1/example/users/{id}"
};
}
rpc CreateUser(CreateUserRequest) returns (User) {
option (google.api.http) = {
post: "/v1/example/users"
body: "*"
};
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
message GetUserRequest {
string id = 1;
}
message CreateUserRequest {
string name = 1;
}
message User {
string id = 1;
string name = 2;
}
3.gRPC stub 생성
protoc
컴파일러를 사용하여 .proto
파일을 빌드하고 .pb.go
파일을 만듭니다.
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:. \
service.proto
path/to/your_service.pb.go
가 생성됩니다.
4. reverse-proxy 생성
protoc
컴파일러를 사용하여 .proto
파일을 빌드하고 .pb.gw.go
파일을 만듭니다.
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--grpc-gateway_out=logtostderr=true:. \
service.proto
5. Gateway / 서버 생성
gateway/서버를 아래와 같이 작성합니다.
greeter_server/main.gopackage main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "../proto"
)
const (
port = ":5001"
)
// server is used to implement HelloWorldServer.
type server struct{}
// SayHello implements HelloWorldServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.Name)
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
// GetUser
func (s *server) GetUser(ctx context.Context, in *pb.GetUserRequest) (*pb.User, error) {
log.Printf("Received: %v", in.Id)
return &pb.User{
Id: in.Id,
Name: "SampleUser"}, nil
}
// CreateUser
func (s *server) CreateUser(ctx context.Context, in *pb.CreateUserRequest) (*pb.User, error) {
log.Printf("Received: %v", in.Name)
return &pb.User{
Id: "123",
Name: in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterHelloWorldServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
greeter_gateway/main.gopackage main
import (
"flag"
"fmt"
"net/http"
"github.com/golang/glog"
"golang.org/x/net/context"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"google.golang.org/grpc"
gw "../proto"
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
endpoint := fmt.Sprintf("localhost:5001")
err := gw.RegisterHelloWorldServiceHandlerFromEndpoint(ctx, mux, endpoint, opts)
if err != nil {
return err
}
return http.ListenAndServe(":5000", mux)
}
func main() {
flag.Parse()
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
동작 확인
게이트웨이와 서버를 실행해 봅시다.
go run greeter_gateway/main.go
다른 터미널을 열고 다음을 수행합니다.
go run greeter_server/main.go
curl
명령으로 REST API를 실행해 봅시다.
예상대로 데이터를 얻을 수 있습니다.
사용자 이름 (nakata)을 넣고 sayhello
로 GET합니다.
curl -X GET http://localhost:5000/v1/example/sayhello/nakata
{"message":"Hello nakata"}
사용자 ID(10)를 넣고 users
로 GET합니다.
curl -X GET http://localhost:5000/v1/example/users/10
{"id":"10","name":"SampleUser"}
사용자 이름 (nakata)을 넣고 users
로 POST합니다.
curl -X POST http://localhost:5000/v1/example/users -d '{"name":"nakata"}'
{"id":"123","name":"nakata"}
결론
gRPC에 해당하는 RESTful API를 자동 생성했습니다.
gRPC 서버의 구현을 curl 명령으로 확인할 수있었습니다.
Reference
이 문제에 관하여(Golang grpc-gateway에서 RESTful API 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/ryu3/items/b2882d4f45c7f8485030
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
gRPC에 대해서는 별도 기사를 작성하고 있으므로 여기를 참조하십시오.
htps : // m / 류 3 / ms / d5 ~ c28 a 412 a 06 e 17 e f98
grpc-gateway 개요
grpc-gateway는 gRPC로 작성된 API를 JSON over HTTP API로 변환합니다. 코드 생성기 역할을 하며 특정 역방향 프록시 서버를 생성합니다. 아래 그림을 참조하십시오.
설치
아래 명령으로 설치하십시오.
$ go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
$ go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
$ go get -u -v github.com/golang/protobuf/protoc-gen-go
사용해 보자.
1.gRPC 서비스 정의
proto/service.protosyntax = "proto3";
package example;
import "google/api/annotations.proto";
service HelloWorldService {
rpc SayHello (HelloRequest) returns (HelloReply) {
}
rpc GetUser(GetUserRequest) returns (User) {
}
rpc CreateUser(CreateUserRequest) returns (User) {
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
message GetUserRequest {
string id = 1;
}
message CreateUserRequest {
string name = 1;
}
message User {
string id = 1;
string name = 2;
}
2. google.api.http를 추가합니다.
service.protosyntax = "proto3";
package example;
import "google/api/annotations.proto";
service HelloWorldService {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
get: "/v1/example/sayhello/{name}"
};
}
rpc GetUser(GetUserRequest) returns (User) {
option (google.api.http) = {
get: "/v1/example/users/{id}"
};
}
rpc CreateUser(CreateUserRequest) returns (User) {
option (google.api.http) = {
post: "/v1/example/users"
body: "*"
};
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
message GetUserRequest {
string id = 1;
}
message CreateUserRequest {
string name = 1;
}
message User {
string id = 1;
string name = 2;
}
3.gRPC stub 생성
protoc
컴파일러를 사용하여 .proto
파일을 빌드하고 .pb.go
파일을 만듭니다.
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:. \
service.proto
path/to/your_service.pb.go
가 생성됩니다.
4. reverse-proxy 생성
protoc
컴파일러를 사용하여 .proto
파일을 빌드하고 .pb.gw.go
파일을 만듭니다.
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--grpc-gateway_out=logtostderr=true:. \
service.proto
5. Gateway / 서버 생성
gateway/서버를 아래와 같이 작성합니다.
greeter_server/main.gopackage main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "../proto"
)
const (
port = ":5001"
)
// server is used to implement HelloWorldServer.
type server struct{}
// SayHello implements HelloWorldServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.Name)
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
// GetUser
func (s *server) GetUser(ctx context.Context, in *pb.GetUserRequest) (*pb.User, error) {
log.Printf("Received: %v", in.Id)
return &pb.User{
Id: in.Id,
Name: "SampleUser"}, nil
}
// CreateUser
func (s *server) CreateUser(ctx context.Context, in *pb.CreateUserRequest) (*pb.User, error) {
log.Printf("Received: %v", in.Name)
return &pb.User{
Id: "123",
Name: in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterHelloWorldServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
greeter_gateway/main.gopackage main
import (
"flag"
"fmt"
"net/http"
"github.com/golang/glog"
"golang.org/x/net/context"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"google.golang.org/grpc"
gw "../proto"
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
endpoint := fmt.Sprintf("localhost:5001")
err := gw.RegisterHelloWorldServiceHandlerFromEndpoint(ctx, mux, endpoint, opts)
if err != nil {
return err
}
return http.ListenAndServe(":5000", mux)
}
func main() {
flag.Parse()
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
동작 확인
게이트웨이와 서버를 실행해 봅시다.
go run greeter_gateway/main.go
다른 터미널을 열고 다음을 수행합니다.
go run greeter_server/main.go
curl
명령으로 REST API를 실행해 봅시다.
예상대로 데이터를 얻을 수 있습니다.
사용자 이름 (nakata)을 넣고 sayhello
로 GET합니다.
curl -X GET http://localhost:5000/v1/example/sayhello/nakata
{"message":"Hello nakata"}
사용자 ID(10)를 넣고 users
로 GET합니다.
curl -X GET http://localhost:5000/v1/example/users/10
{"id":"10","name":"SampleUser"}
사용자 이름 (nakata)을 넣고 users
로 POST합니다.
curl -X POST http://localhost:5000/v1/example/users -d '{"name":"nakata"}'
{"id":"123","name":"nakata"}
결론
gRPC에 해당하는 RESTful API를 자동 생성했습니다.
gRPC 서버의 구현을 curl 명령으로 확인할 수있었습니다.
Reference
이 문제에 관하여(Golang grpc-gateway에서 RESTful API 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/ryu3/items/b2882d4f45c7f8485030
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
아래 명령으로 설치하십시오.
$ go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
$ go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
$ go get -u -v github.com/golang/protobuf/protoc-gen-go
사용해 보자.
1.gRPC 서비스 정의
proto/service.protosyntax = "proto3";
package example;
import "google/api/annotations.proto";
service HelloWorldService {
rpc SayHello (HelloRequest) returns (HelloReply) {
}
rpc GetUser(GetUserRequest) returns (User) {
}
rpc CreateUser(CreateUserRequest) returns (User) {
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
message GetUserRequest {
string id = 1;
}
message CreateUserRequest {
string name = 1;
}
message User {
string id = 1;
string name = 2;
}
2. google.api.http를 추가합니다.
service.protosyntax = "proto3";
package example;
import "google/api/annotations.proto";
service HelloWorldService {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
get: "/v1/example/sayhello/{name}"
};
}
rpc GetUser(GetUserRequest) returns (User) {
option (google.api.http) = {
get: "/v1/example/users/{id}"
};
}
rpc CreateUser(CreateUserRequest) returns (User) {
option (google.api.http) = {
post: "/v1/example/users"
body: "*"
};
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
message GetUserRequest {
string id = 1;
}
message CreateUserRequest {
string name = 1;
}
message User {
string id = 1;
string name = 2;
}
3.gRPC stub 생성
protoc
컴파일러를 사용하여 .proto
파일을 빌드하고 .pb.go
파일을 만듭니다.
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:. \
service.proto
path/to/your_service.pb.go
가 생성됩니다.
4. reverse-proxy 생성
protoc
컴파일러를 사용하여 .proto
파일을 빌드하고 .pb.gw.go
파일을 만듭니다.
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--grpc-gateway_out=logtostderr=true:. \
service.proto
5. Gateway / 서버 생성
gateway/서버를 아래와 같이 작성합니다.
greeter_server/main.gopackage main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "../proto"
)
const (
port = ":5001"
)
// server is used to implement HelloWorldServer.
type server struct{}
// SayHello implements HelloWorldServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.Name)
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
// GetUser
func (s *server) GetUser(ctx context.Context, in *pb.GetUserRequest) (*pb.User, error) {
log.Printf("Received: %v", in.Id)
return &pb.User{
Id: in.Id,
Name: "SampleUser"}, nil
}
// CreateUser
func (s *server) CreateUser(ctx context.Context, in *pb.CreateUserRequest) (*pb.User, error) {
log.Printf("Received: %v", in.Name)
return &pb.User{
Id: "123",
Name: in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterHelloWorldServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
greeter_gateway/main.gopackage main
import (
"flag"
"fmt"
"net/http"
"github.com/golang/glog"
"golang.org/x/net/context"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"google.golang.org/grpc"
gw "../proto"
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
endpoint := fmt.Sprintf("localhost:5001")
err := gw.RegisterHelloWorldServiceHandlerFromEndpoint(ctx, mux, endpoint, opts)
if err != nil {
return err
}
return http.ListenAndServe(":5000", mux)
}
func main() {
flag.Parse()
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
동작 확인
게이트웨이와 서버를 실행해 봅시다.
go run greeter_gateway/main.go
다른 터미널을 열고 다음을 수행합니다.
go run greeter_server/main.go
curl
명령으로 REST API를 실행해 봅시다.
예상대로 데이터를 얻을 수 있습니다.
사용자 이름 (nakata)을 넣고 sayhello
로 GET합니다.
curl -X GET http://localhost:5000/v1/example/sayhello/nakata
{"message":"Hello nakata"}
사용자 ID(10)를 넣고 users
로 GET합니다.
curl -X GET http://localhost:5000/v1/example/users/10
{"id":"10","name":"SampleUser"}
사용자 이름 (nakata)을 넣고 users
로 POST합니다.
curl -X POST http://localhost:5000/v1/example/users -d '{"name":"nakata"}'
{"id":"123","name":"nakata"}
결론
gRPC에 해당하는 RESTful API를 자동 생성했습니다.
gRPC 서버의 구현을 curl 명령으로 확인할 수있었습니다.
Reference
이 문제에 관하여(Golang grpc-gateway에서 RESTful API 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/ryu3/items/b2882d4f45c7f8485030
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
syntax = "proto3";
package example;
import "google/api/annotations.proto";
service HelloWorldService {
rpc SayHello (HelloRequest) returns (HelloReply) {
}
rpc GetUser(GetUserRequest) returns (User) {
}
rpc CreateUser(CreateUserRequest) returns (User) {
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
message GetUserRequest {
string id = 1;
}
message CreateUserRequest {
string name = 1;
}
message User {
string id = 1;
string name = 2;
}
syntax = "proto3";
package example;
import "google/api/annotations.proto";
service HelloWorldService {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
get: "/v1/example/sayhello/{name}"
};
}
rpc GetUser(GetUserRequest) returns (User) {
option (google.api.http) = {
get: "/v1/example/users/{id}"
};
}
rpc CreateUser(CreateUserRequest) returns (User) {
option (google.api.http) = {
post: "/v1/example/users"
body: "*"
};
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
message GetUserRequest {
string id = 1;
}
message CreateUserRequest {
string name = 1;
}
message User {
string id = 1;
string name = 2;
}
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:. \
service.proto
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--grpc-gateway_out=logtostderr=true:. \
service.proto
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "../proto"
)
const (
port = ":5001"
)
// server is used to implement HelloWorldServer.
type server struct{}
// SayHello implements HelloWorldServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.Name)
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
// GetUser
func (s *server) GetUser(ctx context.Context, in *pb.GetUserRequest) (*pb.User, error) {
log.Printf("Received: %v", in.Id)
return &pb.User{
Id: in.Id,
Name: "SampleUser"}, nil
}
// CreateUser
func (s *server) CreateUser(ctx context.Context, in *pb.CreateUserRequest) (*pb.User, error) {
log.Printf("Received: %v", in.Name)
return &pb.User{
Id: "123",
Name: in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterHelloWorldServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
package main
import (
"flag"
"fmt"
"net/http"
"github.com/golang/glog"
"golang.org/x/net/context"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"google.golang.org/grpc"
gw "../proto"
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
endpoint := fmt.Sprintf("localhost:5001")
err := gw.RegisterHelloWorldServiceHandlerFromEndpoint(ctx, mux, endpoint, opts)
if err != nil {
return err
}
return http.ListenAndServe(":5000", mux)
}
func main() {
flag.Parse()
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
go run greeter_gateway/main.go
go run greeter_server/main.go
curl -X GET http://localhost:5000/v1/example/sayhello/nakata
{"message":"Hello nakata"}
curl -X GET http://localhost:5000/v1/example/users/10
{"id":"10","name":"SampleUser"}
curl -X POST http://localhost:5000/v1/example/users -d '{"name":"nakata"}'
{"id":"123","name":"nakata"}
gRPC에 해당하는 RESTful API를 자동 생성했습니다.
gRPC 서버의 구현을 curl 명령으로 확인할 수있었습니다.
Reference
이 문제에 관하여(Golang grpc-gateway에서 RESTful API 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/ryu3/items/b2882d4f45c7f8485030텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)