本文整理汇总了Golang中github.com/corestoreio/csfw/net/ctxhttp.HandlerFunc函数的典型用法代码示例。如果您正苦于以下问题:Golang HandlerFunc函数的具体用法?Golang HandlerFunc怎么用?Golang HandlerFunc使用的例子?那么恭喜您, 这里精选的函数代码示例或许可以为您提供帮助。
在下文中一共展示了HandlerFunc函数的20个代码示例,这些例子默认根据受欢迎程度排序。您可以为喜欢或者感觉有用的代码点赞,您的评价将有助于我们的系统推荐出更棒的Golang代码示例。
示例1: WithParseAndValidate
// WithParseAndValidate represent a middleware handler. For POST or
// PUT requests, it also parses the request body as a form. The extracted valid
// token will be added to the context. The extracted token will be checked
// against the Blacklist. errHandler is an optional argument. Only the first
// item in the slice will be considered. Default errHandler is:
//
// http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
//
// ProTip: Instead of passing the token as an HTML Header you can also add the token
// to a form (multipart/form-data) with an input name of access_token. If the
// token cannot be found within the Header the fallback triggers the lookup within the form.
func (s *Service) WithParseAndValidate(errHandler ...ctxhttp.Handler) ctxhttp.Middleware {
var errH ctxhttp.Handler
errH = ctxhttp.HandlerFunc(defaultTokenErrorHandler)
if len(errHandler) == 1 && errHandler[0] != nil {
errH = errHandler[0]
}
return func(h ctxhttp.Handler) ctxhttp.Handler {
return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
token, err := jwt.ParseFromRequest(r, s.keyFunc)
var inBL bool
if token != nil {
inBL = s.Blacklist.Has(token.Raw)
}
if token != nil && err == nil && token.Valid && !inBL {
return h.ServeHTTPContext(NewContext(ctx, token), w, r)
}
if PkgLog.IsDebug() {
PkgLog.Debug("ctxjwt.Service.Authenticate", "err", err, "token", token, "inBlacklist", inBL)
}
return errH.ServeHTTPContext(NewContextWithError(ctx, err), w, r)
})
}
}
开发者ID:levcom,项目名称:csfw,代码行数:36,代码来源:service_middleware.go
示例2: TestWithAccessLog
func TestWithAccessLog(t *testing.T) {
var buf bytes.Buffer
defer buf.Reset()
ctx := ctxlog.NewContext(context.Background(), log.NewStdLogger(log.SetStdWriter(&buf)))
finalH := ctxhttp.Chain(
ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
w.WriteHeader(http.StatusTeapot)
_, err := w.Write([]byte{'1', '2', '3'})
time.Sleep(time.Millisecond)
return err
}),
ctxhttp.WithAccessLog(),
)
r, _ := http.NewRequest("GET", "/gopherine", nil)
r.RemoteAddr = "127.0.0.1"
r.Header.Set("User-Agent", "Mozilla")
r.Header.Set("Referer", "http://rustlang.org")
w := httptest.NewRecorder()
if err := finalH.ServeHTTPContext(ctx, w, r); err != nil {
t.Fatal(err)
}
assert.Exactly(t, `123`, w.Body.String())
assert.Exactly(t, http.StatusTeapot, w.Code)
want1 := `request error: "" method: "GET" uri: "/gopherine" type: "access" status: "error" status_code: 418 duration:`
want2 := `size: 3 remote_addr: "127.0.0.1" user_agent: "Mozilla" referer: "http://rustlang.org"`
assert.Contains(t, buf.String(), want1)
assert.Contains(t, buf.String(), want2)
}
开发者ID:levcom,项目名称:csfw,代码行数:34,代码来源:middleware_access_test.go
示例3: TestWithParseAndValidateInBlackList
func TestWithParseAndValidateInBlackList(t *testing.T) {
bl := &testRealBL{}
jm, err := ctxjwt.NewService(
ctxjwt.WithBlacklist(bl),
)
assert.NoError(t, err)
theToken, _, err := jm.GenerateToken(nil)
bl.token = theToken
assert.NoError(t, err)
assert.NotEmpty(t, theToken)
req, err := http.NewRequest("GET", "http://auth.xyz", nil)
assert.NoError(t, err)
ctxjwt.SetHeaderAuthorization(req, theToken)
finalHandler := ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
w.WriteHeader(http.StatusTeapot)
return nil
})
authHandler := jm.WithParseAndValidate()(finalHandler)
wRec := httptest.NewRecorder()
assert.NoError(t, authHandler.ServeHTTPContext(context.Background(), wRec, req))
assert.NotEqual(t, http.StatusTeapot, wRec.Code)
assert.Equal(t, http.StatusUnauthorized, wRec.Code)
}
开发者ID:levcom,项目名称:csfw,代码行数:28,代码来源:service_test.go
示例4: TestWithParseAndValidateSuccess
func TestWithParseAndValidateSuccess(t *testing.T) {
jm, err := ctxjwt.NewService()
assert.NoError(t, err)
theToken, _, err := jm.GenerateToken(map[string]interface{}{
"xfoo": "bar",
"zfoo": 4711,
})
assert.NoError(t, err)
assert.NotEmpty(t, theToken)
req, err := http.NewRequest("GET", "http://auth.xyz", nil)
assert.NoError(t, err)
ctxjwt.SetHeaderAuthorization(req, theToken)
finalHandler := ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
w.WriteHeader(http.StatusTeapot)
fmt.Fprintf(w, "I'm more of a coffee pot")
ctxToken, err := ctxjwt.FromContext(ctx)
assert.NoError(t, err)
assert.NotNil(t, ctxToken)
assert.Exactly(t, "bar", ctxToken.Claims["xfoo"].(string))
return nil
})
authHandler := jm.WithParseAndValidate()(finalHandler)
wRec := httptest.NewRecorder()
assert.NoError(t, authHandler.ServeHTTPContext(context.Background(), wRec, req))
assert.Equal(t, http.StatusTeapot, wRec.Code)
assert.Equal(t, `I'm more of a coffee pot`, wRec.Body.String())
}
开发者ID:levcom,项目名称:csfw,代码行数:33,代码来源:service_test.go
示例5: WithIsCountryAllowedByIP
// WithIsCountryAllowedByIP a more advanced function. It expects from the context
// the store.ManagerReader ...
func (s *Service) WithIsCountryAllowedByIP() ctxhttp.Middleware {
return func(h ctxhttp.Handler) ctxhttp.Handler {
return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
_, requestedStore, err := store.FromContextReader(ctx)
if err != nil {
if PkgLog.IsDebug() {
PkgLog.Debug("geoip.WithCountryByIP.FromContextManagerReader", "err", err)
}
return errgo.Mask(err)
}
var ipCountry *IPCountry
ctx, ipCountry, err = s.newContextCountryByIP(ctx, r)
if err != nil {
ctx = NewContextWithError(ctx, err)
return h.ServeHTTPContext(ctx, w, r)
}
allowedCountries, err := directory.AllowedCountries(requestedStore.Config)
if err != nil {
if PkgLog.IsDebug() {
PkgLog.Debug("geoip.WithCountryByIP.directory.AllowedCountries", "err", err, "st.Config", requestedStore.Config)
}
return errgo.Mask(err)
}
if false == s.IsAllowed(requestedStore, ipCountry, allowedCountries, r) {
h = s.altHandlerByID(requestedStore)
}
return h.ServeHTTPContext(ctx, w, r)
})
}
}
开发者ID:levcom,项目名称:csfw,代码行数:37,代码来源:service.go
示例6: Benchmark_WithValidateBaseUrl
// Benchmark_WithValidateBaseUrl-4 3000 489089 ns/op 188333 B/op 272 allocs/op => with debug enabled
// Benchmark_WithValidateBaseUrl-4 200000 8925 ns/op 2924 B/op 49 allocs/op => no debug
func Benchmark_WithValidateBaseUrl(b *testing.B) {
// todo: there is room for optimization with disabled debugging. too many allocs
store.PkgLog.SetLevel(log.StdLevelInfo)
req, err := http.NewRequest(httputils.MethodGet, "https://corestore.io/customer/comments/view?id=1916#tab=ratings", nil)
if err != nil {
b.Fatal(err)
}
finalHandler := store.WithValidateBaseURL(middlewareConfigReader)(ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
return errors.New("This handler should not be called!")
}))
want := "https://www.corestore.io/customer/comments/view?id=1916#tab=ratings"
rec := httptest.NewRecorder()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := finalHandler.ServeHTTPContext(middlewareCtxStoreService, rec, req); err != nil {
b.Error(err)
}
if rec.HeaderMap.Get("Location") != want {
b.Errorf("Have: %s\nWant: %s", rec.HeaderMap.Get("Location"), want)
}
rec.HeaderMap = nil
}
}
开发者ID:levcom,项目名称:csfw,代码行数:27,代码来源:middleware_bm_test.go
示例7: finalHandlerWithValidateBaseURL
func finalHandlerWithValidateBaseURL(t *testing.T) ctxhttp.Handler {
return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
assert.NotNil(t, ctx)
assert.NotNil(t, w)
assert.NotNil(t, r)
assert.Empty(t, w.Header().Get("Location"))
return nil
})
}
开发者ID:levcom,项目名称:csfw,代码行数:9,代码来源:middleware_test.go
示例8: ipErrorFinalHandler
func ipErrorFinalHandler(t *testing.T) ctxhttp.Handler {
return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
ipc, err, ok := geoip.FromContextCountry(ctx)
assert.Nil(t, ipc)
assert.True(t, ok)
assert.EqualError(t, err, geoip.ErrCannotGetRemoteAddr.Error())
return nil
})
}
开发者ID:levcom,项目名称:csfw,代码行数:9,代码来源:service_test.go
示例9: finalInitStoreHandler
func finalInitStoreHandler(t *testing.T, wantStoreCode string) ctxhttp.Handler {
return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
_, haveReqStore, err := store.FromContextReader(ctx)
if err != nil {
return err
}
assert.Exactly(t, wantStoreCode, haveReqStore.StoreCode())
return nil
})
}
开发者ID:levcom,项目名称:csfw,代码行数:10,代码来源:middleware_test.go
示例10: TestCustomHTTPRateLimitHandlers
func TestCustomHTTPRateLimitHandlers(t *testing.T) {
limiter := ctxthrottled.HTTPRateLimit{
RateLimiter: &stubLimiter{},
VaryBy: &pathGetter{},
DeniedHandler: ctxhttp.HandlerFunc(func(_ context.Context, w http.ResponseWriter, _ *http.Request) error {
http.Error(w, "custom limit exceeded", 400)
return nil
}),
}
handler := limiter.WithRateLimit(nil, ctxhttp.HandlerFunc(func(_ context.Context, w http.ResponseWriter, _ *http.Request) error {
w.WriteHeader(200)
return nil
}))
runHTTPTestCases(t, handler, []httpTestCase{
{"limit", 400, map[string]string{}},
{"error", 500, map[string]string{}},
})
}
开发者ID:levcom,项目名称:csfw,代码行数:20,代码来源:http_test.go
示例11: finalHandlerFinland
func finalHandlerFinland(t *testing.T) ctxhttp.Handler {
return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
ipc, err, ok := geoip.FromContextCountry(ctx)
assert.NotNil(t, ipc)
assert.True(t, ok)
assert.NoError(t, err)
assert.Exactly(t, "2a02:d200::", ipc.IP.String())
assert.Exactly(t, "FI", ipc.Country.Country.IsoCode)
return nil
})
}
开发者ID:levcom,项目名称:csfw,代码行数:11,代码来源:service_test.go
示例12: WithValidateBaseURL
// WithValidateBaseURL is a middleware which checks if the request base URL
// is equal to the one store in the configuration, if not
// i.e. redirect from http://example.com/store/ to http://www.example.com/store/
// @see app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php
func WithValidateBaseURL(cr config.ReaderPubSuber) ctxhttp.Middleware {
// Having the GetBool command here, means you must restart the app to take
// changes in effect. @todo refactor and use pub/sub to automatically change
// the isRedirectToBase value.
checkBaseURL, err := cr.GetBool(config.Path(PathRedirectToBase)) // scope default
if config.NotKeyNotFoundError(err) && PkgLog.IsDebug() {
PkgLog.Debug("ctxhttp.WithValidateBaseUrl.GetBool", "err", err, "path", PathRedirectToBase)
}
redirectCode := http.StatusMovedPermanently
if rc, err := cr.GetInt(config.Path(PathRedirectToBase)); rc != redirectCode && false == config.NotKeyNotFoundError(err) {
redirectCode = http.StatusFound
}
return func(h ctxhttp.Handler) ctxhttp.Handler {
return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
if checkBaseURL && r.Method != "POST" {
_, requestedStore, err := FromContextReader(ctx)
if err != nil {
if PkgLog.IsDebug() {
PkgLog.Debug("ctxhttp.WithValidateBaseUrl.FromContextServiceReader", "err", err, "ctx", ctx)
}
return errgo.Mask(err)
}
baseURL, err := requestedStore.BaseURL(config.URLTypeWeb, requestedStore.IsCurrentlySecure(r))
if err != nil {
if PkgLog.IsDebug() {
PkgLog.Debug("ctxhttp.WithValidateBaseUrl.requestedStore.BaseURL", "err", err, "ctx", ctx)
}
return errgo.Mask(err)
}
if err := httputils.IsBaseURLCorrect(r, &baseURL); err != nil {
if PkgLog.IsDebug() {
PkgLog.Debug("store.WithValidateBaseUrl.IsBaseUrlCorrect.error", "err", err, "baseURL", baseURL, "request", r)
}
baseURL.Path = r.URL.Path
baseURL.RawPath = r.URL.RawPath
baseURL.RawQuery = r.URL.RawQuery
baseURL.Fragment = r.URL.Fragment
http.Redirect(w, r, (&baseURL).String(), redirectCode)
return nil
}
}
return h.ServeHTTPContext(ctx, w, r)
})
}
}
开发者ID:levcom,项目名称:csfw,代码行数:57,代码来源:middleware.go
示例13: WithCountryByIP
// WithCountryByIP is a simple middleware which detects the country via an IP
// address. With the detected country a new tree context.Context gets created.
func (s *Service) WithCountryByIP() ctxhttp.Middleware {
return func(h ctxhttp.Handler) ctxhttp.Handler {
return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
var err error
ctx, _, err = s.newContextCountryByIP(ctx, r)
if err != nil {
ctx = NewContextWithError(ctx, err)
}
return h.ServeHTTPContext(ctx, w, r)
})
}
}
开发者ID:levcom,项目名称:csfw,代码行数:14,代码来源:service.go
示例14: benchValidationHandler
func benchValidationHandler(b *testing.B, wantStoreCode string) ctxhttp.Handler {
return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
_, haveReqStore, err := store.FromContextReader(ctx)
if err != nil {
return err
}
if wantStoreCode != haveReqStore.StoreCode() {
b.Errorf("Want: %s\nHave: %s", wantStoreCode, haveReqStore.StoreCode())
}
return nil
})
}
开发者ID:levcom,项目名称:csfw,代码行数:13,代码来源:middleware_bm_test.go
示例15: TestWithValidateBaseUrl_ActivatedAndShouldRedirectWithGETRequest
func TestWithValidateBaseUrl_ActivatedAndShouldRedirectWithGETRequest(t *testing.T) {
var newReq = func(urlStr string) *http.Request {
req, err := http.NewRequest(httputils.MethodGet, urlStr, nil)
if err != nil {
t.Fatal(err)
}
return req
}
tests := []struct {
rec *httptest.ResponseRecorder
req *http.Request
wantRedirectURL string
}{
{
httptest.NewRecorder(),
newReq("http://corestore.io/catalog/product/view/"),
"http://www.corestore.io/catalog/product/view/",
},
{
httptest.NewRecorder(),
newReq("http://corestore.io/catalog/product/view"),
"http://www.corestore.io/catalog/product/view",
},
{
httptest.NewRecorder(),
newReq("http://corestore.io"),
"http://www.corestore.io",
},
{
httptest.NewRecorder(),
newReq("https://corestore.io/catalog/category/view?catid=1916"),
"https://www.corestore.io/catalog/category/view?catid=1916",
},
{
httptest.NewRecorder(),
newReq("https://corestore.io/customer/comments/view?id=1916#tab=ratings"),
"https://www.corestore.io/customer/comments/view?id=1916#tab=ratings",
},
}
for i, test := range tests {
mw := store.WithValidateBaseURL(middlewareConfigReader)(ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
return fmt.Errorf("This handler should not be called! Iindex %d", i)
}))
assert.NoError(t, mw.ServeHTTPContext(middlewareCtxStoreService, test.rec, test.req), "Index %d", i)
assert.Exactly(t, test.wantRedirectURL, test.rec.HeaderMap.Get("Location"), "Index %d", i)
}
}
开发者ID:levcom,项目名称:csfw,代码行数:50,代码来源:middleware_test.go
示例16: testAuth
func testAuth(t *testing.T, errH ctxhttp.Handler, opts ...ctxjwt.Option) (ctxhttp.Handler, string) {
jm, err := ctxjwt.NewService(opts...)
assert.NoError(t, err)
theToken, _, err := jm.GenerateToken(map[string]interface{}{
"xfoo": "bar",
"zfoo": 4711,
})
assert.NoError(t, err)
final := ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
w.WriteHeader(http.StatusOK)
return nil
})
authHandler := jm.WithParseAndValidate(errH)(final)
return authHandler, theToken
}
开发者ID:levcom,项目名称:csfw,代码行数:16,代码来源:service_test.go
示例17: WithInitStoreByToken
// WithInitStoreByToken is a middleware which initializes a request based store
// via a JSON Web Token.
// Extracts the store.Reader and jwt.Token from context.Context. If the requested
// store is different than the initialized requested store than the new requested
// store will be saved in the context.
func WithInitStoreByToken() ctxhttp.Middleware {
return func(h ctxhttp.Handler) ctxhttp.Handler {
return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
storeService, requestedStore, err := FromContextReader(ctx)
if err != nil {
if PkgLog.IsDebug() {
PkgLog.Debug("store.WithInitStoreByToken.FromContextServiceReader", "err", err, "ctx", ctx)
}
return errgo.Mask(err)
}
token, err := ctxjwt.FromContext(ctx)
if err != nil {
if PkgLog.IsDebug() {
PkgLog.Debug("store.WithInitStoreByToken.ctxjwt.FromContext.err", "err", err, "ctx", ctx)
}
return errgo.Mask(err)
}
scopeOption, err := CodeFromClaim(token.Claims)
if err != nil {
if PkgLog.IsDebug() {
PkgLog.Debug("store.WithInitStoreByToken.StoreCodeFromClaim", "err", err, "token", token, "ctx", ctx)
}
return errgo.Mask(err)
}
newRequestedStore, err := storeService.RequestedStore(scopeOption)
if err != nil {
if PkgLog.IsDebug() {
PkgLog.Debug("store.WithInitStoreByToken.RequestedStore", "err", err, "token", token, "scopeOption", scopeOption, "ctx", ctx)
}
return errgo.Mask(err)
}
if newRequestedStore.StoreID() != requestedStore.StoreID() {
// this may lead to a bug because the previously set storeService and requestedStore
// will still exists and have not been removed.
ctx = NewContextReader(ctx, storeService, newRequestedStore)
}
return h.ServeHTTPContext(ctx, w, r)
})
}
}
开发者ID:levcom,项目名称:csfw,代码行数:52,代码来源:middleware.go
示例18: TestHTTPRateLimit
func TestHTTPRateLimit(t *testing.T) {
limiter := ctxthrottled.HTTPRateLimit{
RateLimiter: &stubLimiter{},
VaryBy: &pathGetter{},
}
handler := limiter.WithRateLimit(nil, ctxhttp.HandlerFunc(func(_ context.Context, w http.ResponseWriter, _ *http.Request) error {
w.WriteHeader(200)
return nil
}))
runHTTPTestCases(t, handler, []httpTestCase{
{"ok", 200, map[string]string{"X-Ratelimit-Limit": "1", "X-Ratelimit-Remaining": "2", "X-Ratelimit-Reset": "60"}},
{"error", 500, map[string]string{}},
{"limit", 429, map[string]string{"Retry-After": "60"}},
})
}
开发者ID:levcom,项目名称:csfw,代码行数:17,代码来源:http_test.go
示例19: TestWithParseAndValidateHTTPErrorHandler
func TestWithParseAndValidateHTTPErrorHandler(t *testing.T) {
authHandler, _ := testAuth(t, ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
tok, err := ctxjwt.FromContext(ctx)
assert.Nil(t, tok)
w.WriteHeader(http.StatusTeapot)
_, err = w.Write([]byte(err.Error()))
return err
}))
req, err := http.NewRequest("GET", "http://auth.xyz", nil)
assert.NoError(t, err)
w := httptest.NewRecorder()
assert.NoError(t, authHandler.ServeHTTPContext(context.Background(), w, req))
assert.Equal(t, http.StatusTeapot, w.Code)
assert.Equal(t, "no token present in request", w.Body.String())
}
开发者ID:levcom,项目名称:csfw,代码行数:17,代码来源:service_test.go
示例20: WithRateLimit
// WithRateLimit wraps an ctxhttp.Handler to limit incoming requests.
// Requests that are not limited will be passed to the handler
// unchanged. Limited requests will be passed to the DeniedHandler.
// X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and
// Retry-After headers will be written to the response based on the
// values in the RateLimitResult.
func (t *HTTPRateLimit) WithRateLimit(rlStore throttled.GCRAStore, h ctxhttp.Handler) ctxhttp.Handler {
if t.Config == nil {
t.Config = config.DefaultManager
}
if t.DeniedHandler == nil {
t.DeniedHandler = DefaultDeniedHandler
}
if t.RateLimiter == nil {
if rlStore == nil {
var err error
rlStore, err = memstore.New(65536)
if err != nil {
panic(err)
}
}
var err error
t.RateLimiter, err = throttled.NewGCRARateLimiter(rlStore, t.quota())
if err != nil {
panic(err)
}
}
return ctxhttp.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
var k string
if t.VaryBy != nil {
k = t.VaryBy.Key(r)
}
limited, context, err := t.RateLimiter.RateLimit(k, 1)
if err != nil {
return err
}
setRateLimitHeaders(w, context)
if !limited {
return h.ServeHTTPContext(ctx, w, r)
}
return t.DeniedHandler.ServeHTTPContext(ctx, w, r)
})
}
开发者ID:levcom,项目名称:csfw,代码行数:52,代码来源:http.go
注:本文中的github.com/corestoreio/csfw/net/ctxhttp.HandlerFunc函数示例整理自Github/MSDocs等源码及文档管理平台,相关代码片段筛选自各路编程大神贡献的开源项目,源码版权归原作者所有,传播和使用请参考对应项目的License;未经允许,请勿转载。 |
请发表评论