अमेज़ॅन एपीआई गेटवे से AWS लेम्बडा के लिए एक क्वेरिस्ट्रिंग या रूट पैरामीटर कैसे पास करें


348

उदाहरण के लिए यदि हम उपयोग करना चाहते हैं

GET /user?name=bob

या

GET /user/bob

आप इन दोनों उदाहरणों को लैम्ब्डा फ़ंक्शन के पैरामीटर के रूप में कैसे पारित करेंगे?

मैंने दस्तावेज़ीकरण में "मैप्ड फ्रॉम" सेट करने के बारे में कुछ देखा, लेकिन मुझे वह सेटिंग एपीआई गेटवे कंसोल में नहीं मिली।

  • method.request.path.parameter-nameएक पथ पैरामीटर के लिए जिसे parameter-nameविधि अनुरोध पृष्ठ में परिभाषित किया गया है।
  • method.request.querystring.parameter-nameparameter-nameविधि अनुरोध पृष्ठ में परिभाषित क्वेरी स्ट्रिंग पैरामीटर के लिए।

भले ही मैंने एक क्वेरी स्ट्रिंग को परिभाषित किया हो, मुझे इनमें से कोई भी विकल्प दिखाई नहीं देता है।

जवाबों:


299

सितंबर 2017 तक, आपको अनुरोध निकाय तक पहुंचने के लिए मैपिंग को कॉन्फ़िगर नहीं करना होगा।

आपको संसाधन के तहत एकीकरण अनुरोध के तहत, "लैम्बडा प्रॉक्सी एकीकरण का उपयोग करें" की जांच करने की आवश्यकता है।

यहां छवि विवरण दर्ज करें

फिर आप क्वेरी पैरामीटर, पथ पैरामीटर और हेडर जैसे उपयोग कर सकेंगे

event['pathParameters']['param1']
event["queryStringParameters"]['queryparam1']
event['requestContext']['identity']['userAgent']
event['requestContext']['identity']['sourceIP']

23
यह एक शानदार टिप है। लेकिन, ध्यान रखें कि लैम्ब्डा प्रॉक्सी इंटीग्रेशन को चालू करने से "विकृत लैम्ब्डा प्रोक्सी रिस्पांस" त्रुटि हो सकती है। इसे ठीक करने का तरीका यहां बताया गया है: stackoverflow.com/questions/43708017/…
हारूनबेकर

6
वहाँ जावा में ऐसा करने के लिए एक तरीका है, जबकि पारदर्शी deserialization कि कार्यान्वयन RequestHandlerप्रदान करता है?
जूता

2
यह सेटिंग कहाँ है
red888

2
@MattWestlake आप उपयोगकर्ता नामक एक संसाधन बनाते हैं और इसके तहत API गेटवे में {name} नामक एक संसाधन होता है।
जोनाथन

3
मैं केवल यह उल्लेख करना चाहता हूं कि इस परिवर्तन के बाद मुझे Amazon API Gateway -> Actions -> Dep API पर जाना होगा और लाइव वातावरण में फिर से तैनाती करनी होगी।
विवरवर्तन

221

इस कार्य को करने के लिए चरण हैं:

एपीआई गेटवे कंसोल के भीतर ...

  1. के लिए जाओ Resources -> Integration Request
  2. टेम्प्लेट ड्रॉपडाउन के बगल में प्लस या एडिट आइकन पर क्लिक करें (विषम मुझे पता है कि टेम्प्लेट फ़ील्ड पहले से खुली है और यहां बटन दबा हुआ दिखता है)
  3. स्पष्ट रूप application/jsonसे सामग्री-प्रकार के क्षेत्र में टाइप करें भले ही वह डिफ़ॉल्ट दिखाता हो (यदि आप ऐसा नहीं करते हैं तो यह नहीं बचाएगा और आपको त्रुटि संदेश नहीं देगा)
  4. इसे इनपुट मैपिंग में डालें { "name": "$input.params('name')" }

  5. टेम्प्लेट ड्रॉपडाउन के बगल में स्थित चेक बॉक्स पर क्लिक करें (मैं मान रहा हूं कि यह आखिर इसे बचाता है)


9
क्या आपने कभी भी URL / जैसे URL पैरामीटर के माध्यम से भेजने के लिए इसे प्राप्त किया था / उपयोगकर्ता / bob जहां मार्ग / उपयोगकर्ता / {उपयोगकर्ता नाम} था? मैंने सभी प्रकार के क्रमपरिवर्तन की कोशिश की है, लेकिन उस काम को करने में असमर्थ रहा हूं।
लुकास

5
क्या कोई जानता है कि क्या कोई आधिकारिक दस्तावेज है? अच्छा होगा कि आप सभी क्वेरी मापदंडों से
गुजरें

6
IOS डेवलपर्स के लिए एक टिप: API गेटवे तब तक क्वेरी डेटा पास नहीं करेगा, जब तक कि आप प्रत्येक वैरिएबल को क्वेरी स्ट्रिंग ('विधि अनुरोध') और API को परिनियोजित नहीं करते। परिनियोजन तक यह कंसोल टेस्ट से काम करता है, लेकिन ऐप के प्रश्नों से कट जाता है।
अलेक्सई वीएमपी

3
@axel यहाँ प्रलेखित है: docs.aws.amazon.com/apigateway/latest/developerguide/…
russau

6
लुकास, मुझे यह / उपयोगकर्ता / {उपयोगकर्ता नाम} पैटर्न का उपयोग करके काम करने के लिए मिला। बस याद रखें कि यदि आपका GET संसाधन पथ / उपयोगकर्ता / {उपयोगकर्ता नाम} है, तो चरण 4 में इनपुट मैपिंग इस तरह दिखती है जैसे "" नाम ":" $ input.params ('उपयोगकर्ता नाम') "}
Gerard

134

मैंने इस मैपिंग टेम्प्लेट का उपयोग बॉडी, हेडर्स, मेथड, पाथ, और URL क्वेरी स्ट्रिंग पैरामीटर्स को लैम्ब्डा इवेंट में प्रदान करने के लिए किया है। मैंने एक ब्लॉग पोस्ट को टेम्पलेट को और अधिक विस्तार से समझाते हुए लिखा: http://kennbrodhagen.net/2015/12/06/how-to-create-a-request-object-for-your-lambda-event-from-api- प्रवेश द्वार /

यहां मैपिंग टेम्प्लेट का उपयोग किया जा सकता है:

{
  "method": "$context.httpMethod",
  "body" : $input.json('$'),
  "headers": {
    #foreach($param in $input.params().header.keySet())
    "$param": "$util.escapeJavaScript($input.params().header.get($param))" #if($foreach.hasNext),#end

    #end
  },
  "queryParams": {
    #foreach($param in $input.params().querystring.keySet())
    "$param": "$util.escapeJavaScript($input.params().querystring.get($param))" #if($foreach.hasNext),#end

    #end
  },
  "pathParams": {
    #foreach($param in $input.params().path.keySet())
    "$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end

    #end
  }  
}

गजब का! मैं अपने हैंडलर के लिए उदारतापूर्वक चीजों को पारित करने के साथ संघर्ष कर रहा था। सबसे अच्छा जवाब यहाँ।
वेंकट डी।

मैंने ऐसा किया, लेकिन मुझे अभी तक कुछ भी नहीं मिल रहा है। इसका दिखावा अपरिभाषित है। हमें URL में पैरामीटर कैसे भेजे जाने चाहिए? और क्या हमें सामान्य GET url परिदृश्य की तरह url में चर नाम निर्दिष्ट करने की आवश्यकता है? कृपया इसमें मेरी सहायता करें।
पार्थप्रतीम नेग

8
नतीजा मुझे नहीं मिला। समस्या यह थी, मैंने मैपिंग को जोड़ा और इसे बचा लिया, और deployएक बार फिर एपी नहीं किया । एक बार जब मैंने नई मैपिंग के साथ एपीआई को तैनात किया, तो यह ठीक काम किया। अनेक अनेक धन्यवाद।
पार्थप्रतीम नेग

@ shashu10 मेरा उत्तर देखें
Matsev

1
मैं आपको यह बताना शुरू नहीं कर सकता कि आपका ब्लॉग कितना उपयोगी है। मैंने "eturn-html-from-aws-api-Gateway" पोस्ट को सबसे पहले पाया और उसका अनुसरण किया, क्योंकि यह वही है जो मुझे चाहिए था। अब मुझे फ़ंक्शन में कुछ मापदंडों को पारित करने और उस पर आधारित HTML को संशोधित करने की आवश्यकता है - और फिर से आप एक वास्तविक गाइड के साथ एक ही हैं! मैंने पाया है कि सभी अन्य गाइड बिंदु को याद करते हैं।
user3685427

41

इन दिनों AWS पर एपीआई गेटवे कंसोल में एक ड्रॉप-डाउन टेम्पलेट शामिल है।

अपने एपीआई के लिए, संसाधन नाम पर क्लिक करें ... फिर जी.ई.टी.

"बॉडी मैपिंग टेम्प्लेट" का विस्तार करें

में टाइप करें

आवेदन / json

सामग्री-प्रकार के लिए (स्पष्ट रूप से टाइप किया जाना चाहिए) और टिक पर क्लिक करें

एक नई विंडो "जनरेट टेम्पलेट" और एक ड्रॉपडाउन (चित्र देखें) शब्दों के साथ खुलेगी।

चुनते हैं

विधि अनुरोध passthrough

यहां छवि विवरण दर्ज करें

फिर save पर क्लिक करें

किसी भी चर तक पहुँचने के लिए, बस निम्न सिंटैक्स का उपयोग करें (यह पायथन है) जैसे URL:

https://yourURL.execute-api.us-west-2.amazonaws.com/prod/confirmReg?token=12345&uid=5

आप चर इस प्रकार प्राप्त कर सकते हैं:

from __future__ import print_function

import boto3
import json

print('Loading function')


def lambda_handler(event, context):
    print(event['params']['querystring']['token'])
    print(event['params']['querystring']['uid'])

इसलिए आपको अपनी इच्छानुसार प्रत्येक चर का स्पष्ट रूप से नाम या नक्शा बनाने की आवश्यकता नहीं है।


अति उत्कृष्ट! कार्यक्षमता सेवा में वहीं है लेकिन चूक गई थी!
हन्नसा

25

अपने लैम्ब्डा फ़ंक्शन के मापदंडों को पारित करने के लिए आपको एपीआई गेटवे अनुरोध और अपने लैम्ब्डा फ़ंक्शन के बीच एक मैपिंग बनाने की आवश्यकता है। मानचित्रण चयनित एपीआई गेटवे संसाधन के Integration Request-> Mapping templatesअनुभाग में किया जाता है ।

एक प्रकार की मैपिंग बनाएं application/json, फिर दाईं ओर आप टेम्पलेट पर क्लिक करें (पेंसिल पर क्लिक करें)।

एक मैपिंग टेम्प्लेट वास्तव में एक वेलोसिटी टेम्प्लेट होता है, जहां आप इफ़, लूप और निश्चित रूप से उस पर प्रिंट वेरिएबल्स का उपयोग कर सकते हैं। टेम्पलेट में इन चरों को इंजेक्ट किया गया है जहां आप व्यक्तिगत रूप से क्वेरिस्ट्रिंग मापदंडों, अनुरोध हेडर आदि का उपयोग कर सकते हैं। निम्नलिखित कोड से आप पूरे क्वेरिस्ट्रिंग को फिर से बना सकते हैं:

{
    "querystring" : "#foreach($key in $input.params().querystring.keySet())#if($foreach.index > 0)&#end$util.urlEncode($key)=$util.urlEncode($input.params().querystring.get($key))#end",
    "body" : $input.json('$')
}

नोट: टेम्पलेट को बचाने के लिए चेक सिंबल पर क्लिक करें। आप अपने संसाधन में "परीक्षण" बटन के साथ अपने परिवर्तनों का परीक्षण कर सकते हैं। लेकिन AWS कंसोल में querystring मापदंडों का परीक्षण करने के लिए आपको Method Requestअपने संसाधन के खंड में पैरामीटर नामों को परिभाषित करना होगा ।

नोट: वेग टेम्प्लेट भाषा के बारे में अधिक जानकारी के लिए वेग उपयोगकर्ता गाइड की जाँच करें ।

फिर अपने लैम्ब्डा टेम्पलेट में आप क्वेरिस्ट्रिंग पार्स करने के लिए निम्नलिखित कर सकते हैं:

var query = require('querystring').parse(event.querystring)
// access parameters with query['foo'] or query.foo

9
यह सबसे अच्छा उपाय है। कृपया करने के लिए याद रखें Actions>Deploy API(मैंने अपना समय इस बात को भुला दिया ...)। संबंधित लैम्ब्डा आरएन तैनाती के तुरंत बाद परिवर्तन ले जाएगा। आप इसमें चेक कर सकते हैं Stages > #stage (like: prod) > Deployment History
लोरेटोपरसी

24

स्वीकार किए गए उत्तर ने मेरे लिए ठीक काम किया है, लेकिन जिमीनेट के जवाब पर विस्तार करते हुए, मैं एक सामान्य टेम्पलेट चाहता था जिसका उपयोग मैं सभी क्वेरी / पाथ / हेडर परमेस (अभी के लिए तार के रूप में) से गुजरने के लिए कर सकता हूं, और मैं निम्नलिखित टेम्पलेट पर आया हूं। मैं इसे यहाँ पोस्ट कर रहा हूँ अगर कोई इसे उपयोगी पाता है:

#set($keys = [])
#foreach($key in $input.params().querystring.keySet())
  #set($success = $keys.add($key))
#end

#foreach($key in $input.params().headers.keySet())
  #if(!$keys.contains($key))
    #set($success = $keys.add($key))
  #end
#end

#foreach($key in $input.params().path.keySet())
  #if(!$keys.contains($key))
    #set($success = $keys.add($key))
  #end
#end

{
#foreach($key in $keys)
  "$key": "$util.escapeJavaScript($input.params($key))"#if($foreach.hasNext),#end
#end
}

1
फैब, मैं POST (JSON बॉडी के साथ) अनुरोधों और क्वेरी स्ट्रिंग्स के साथ GET दोनों के लिए एक ही फ़ंक्शन का उपयोग करने में सक्षम होना चाहता था। एक सपने का काम करता है। धन्यवाद!
मैट फ्लेचर

@बेन क्या यह पूरा खाका है?
nxmohamad

17

यहाँ मेरे अपने सवालों के जवाब देने की कोशिश के तहत , मैं इस चाल में आया।

API गेटवे मैपिंग टेम्प्लेट में, HTTP क्लाइंट द्वारा भेजे गए अनुसार पूरा क्वेरी स्ट्रिंग देने के लिए निम्नलिखित का उपयोग करें:

{
    "querystring": "$input.params().querystring"
}

लाभ यह है कि आपको अपने क्वेरी स्ट्रिंग में पूर्वनिर्धारित मैप की गई कुंजी के एक सेट तक खुद को सीमित करने की आवश्यकता नहीं है। अब आप क्वेरी स्ट्रिंग में किसी भी कुंजी-मान जोड़े को स्वीकार कर सकते हैं, यदि आप इसे संभालना चाहते हैं।

नोट: के अनुसार इस , केवल $input.params(x)के रूप में एक चर VTL टेम्पलेट के लिए उपलब्ध कराया सूचीबद्ध है। यह संभव है कि आंतरिक बदल सकते हैं और querystringअब उपलब्ध नहीं हो सकते हैं।


1
यह अभी भी मई 2017 के रूप में काम करता है लेकिन यह जेएस ऑब्जेक्ट को लौटाता है जो एपीआई गेटवे वास्तविक क्वेरी स्ट्रिंग के बजाय आपके लिए बनाता है। यह मेरे लिए कष्टप्रद है क्योंकि मैं क्वेरी स्ट्रिंग को पार्स में बार-बार बदलने के लिए पार्स करने की कोशिश कर रहा हूं।
टॉम सालेबा

11

अब आपको लैम्ब्डा के लिए नए प्रॉक्सी एकीकरण प्रकार का उपयोग करने में सक्षम होना चाहिए, ताकि कॉन्फ़िगर मैपिंग के बजाय स्वचालित रूप से मानक आकार में पूर्ण अनुरोध प्राप्त हो सके।

देखें: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-set-up-lambda-proxy-integration-on-on प्रॉक्सी-संसाधन


1
मुझे यकीन नहीं है कि क्यों, लेकिन प्रॉक्सी एकीकरण आमतौर पर मेरे लिए काम नहीं करता है। मुझे इसे अपने द्वारा बनाए गए नवीनतम एपीआई से निकालना था।
गुस्तावो स्ट्रूबे

वही ^ इसके अलावा मैं एपीआई गेटवे के साथ कोर मुद्दों था। एडब्ल्यूएस डॉक्स के साथ-साथ मैं कोर को काम करने में सक्षम नहीं था। हालाँकि मुझे 2015 के मध्य से एक पुराना माध्यम लेख मिला था जिसमें कोर का गठन करने का एक मैनुअल तरीका था और यह काम किया।
स्टीफन टेट्रौल्ट


5

यहाँ बहुत सारे उत्तर महान हैं। लेकिन मुझे कुछ सरल चाहिए था। मैं कुछ ऐसा चाहता था जो मुफ्त में "हैलो वर्ल्ड" के नमूने के साथ काम करे। इसका मतलब है कि मैं चाहता था कि एक साधारण अनुरोध निकाय का निर्माण हो जो क्वेरी स्ट्रिंग से मेल खाता हो:

{
#foreach($param in $input.params().querystring.keySet())
  "$param": "$util.escapeJavaScript($input.params().querystring.get($param))" #if($foreach.hasNext),#end
#end
}

मुझे लगता है कि शीर्ष उत्तर कुछ वास्तविक निर्माण करते समय अधिक उपयोगी होता है, लेकिन AWS से टेम्पलेट का उपयोग करके एक त्वरित हैलो वर्ल्ड चलाने के लिए यह बहुत अच्छा काम करता है।


4

निम्नलिखित पैरामीटर-मैपिंग उदाहरण सभी मापदंडों को पारित करता है, जिसमें एक JSON कोड लोड के माध्यम से एकीकरण समापन बिंदु के माध्यम से पथ, क्वेरिस्ट्रिंग और हेडर शामिल हैं।

#set($allParams = $input.params())
{
  "params" : {
    #foreach($type in $allParams.keySet())
    #set($params = $allParams.get($type))
    "$type" : {
      #foreach($paramName in $params.keySet())
      "$paramName" : "$util.escapeJavaScript($params.get($paramName))"
      #if($foreach.hasNext),#end
      #end
    }
    #if($foreach.hasNext),#end
    #end
  }
}

वास्तव में, यह मैपिंग टेम्प्लेट पेलोड में सभी अनुरोध मापदंडों को निम्नानुसार उल्लिखित करता है:

{
  "parameters" : {
     "path" : {    
       "path_name" : "path_value", 
       ...
     }
     "header" : {  
       "header_name" : "header_value",
       ...
     }
     'querystring" : {
       "querystring_name" : "querystring_value",
       ...
     }
   }
}

अमेज़न एपीआई गेटवे डेवलपर गाइड से कॉपी किया गया


2

क्वेरी स्ट्रिंग लैम्बडा में जावास्क्रिप्ट में पार्स करने के लिए सीधे आगे है

GET / उपयोगकर्ता के लिए? नाम = बॉब

 var name = event.params.querystring.name;

हालांकि यह GET उपयोगकर्ता / बॉब प्रश्न को हल नहीं करता है।


इसकी घटना ।queryStringParameters.name
Neo

मुझे करना थाevent.queryStringParameters.name
एंडर्स किट्सन

2

@ जोनाथन के उत्तर के रूप में, मार्क के बाद इंटीग्रेशन रिक्वेस्ट में लैम्ब्डा प्रोक्सी इंटीग्रेशन का उपयोग करें , अपने सोर्स कोड में आपको फॉर्मेट के रूप में नीचे दिए गए 502 बैड गेटवे एरर को लागू करना चाहिए ।

नोडजेएस 8.10:

exports.handler = async (event, context, callback) => {
  // TODO: You could get path, parameter, headers, body value from this
  const { path, queryStringParameters, headers, body } = event;

  const response = {
    "statusCode": 200,
    "headers": {
      "Content-Type": "application/json"
    },
    "body": JSON.stringify({
      path, 
      query: queryStringParameters,
      headers,
      body: JSON.parse(body)
    }),
    "isBase64Encoded": false
  };

  return response;
};

अपने एपीआई को फिर से चलाने से पहले एपीआई गेटवे पर अपने संसाधन को तैनात करना न भूलें । रिस्पांस JSON सिर्फ वापसी जो शरीर में सेट सही है। इसलिए, आप ईवेंट से पथ, पैरामीटर, हेडर, बॉडी वैल्यू प्राप्त कर सकते हैं

const {path, queryStringParameters, headers, body} = घटना;


1

लैम्ब्डा फ़ंक्शन JSON इनपुट की अपेक्षा करता है, इसलिए क्वेरी स्ट्रिंग को पार्स करने की आवश्यकता है। समाधान मैपिंग टेम्प्लेट का उपयोग करके JSON के क्वेरी स्ट्रिंग को बदलना है।
मैंने इसका उपयोग C # .NET कोर के लिए किया था, इसलिए अपेक्षित इनपुट को "queryStringParameters" पैरामीटर के साथ JSON होना चाहिए।
इसे प्राप्त करने के लिए नीचे दिए गए इन 4 चरणों का पालन करें:

  1. अपने API गेटवे संसाधन का मैपिंग टेम्प्लेट खोलें और नई application/jsonसामग्री जोड़ें-

एपीआई गेटवे मैपिंग टेम्प्लेट

  1. नीचे दिए गए टेम्पलेट को कॉपी करें, जो JSON में क्वेरी स्ट्रिंग को पार्स करता है, और इसे मैपिंग टेम्प्लेट में पेस्ट करता है:

    {
    "queryStringParameters": {#foreach($key in $input.params().querystring.keySet())#if($foreach.index > 0),#end"$key":"$input.params().querystring.get($key)"#end}
    }
    
  2. एपीआई गेटवे में, अपने लैम्ब्डा फ़ंक्शन को कॉल करें और निम्न क्वेरी स्ट्रिंग (उदाहरण के लिए) जोड़ें: param1=111&param2=222&param3=333

  3. मैपिंग टेम्प्लेट को नीचे JSON आउटपुट बनाना चाहिए, जो आपके लैम्ब्डा फ़ंक्शन के लिए इनपुट है।

    {
    "queryStringParameters": {"param3":"333","param1":"111","param2":"222"}
    }
    
  4. हो गया। इस बिंदु से, आपके लैम्ब्डा फ़ंक्शन का तर्क क्वेरी स्ट्रिंग मापदंडों का उपयोग कर सकता है।
    सौभाग्य!


0

आप लैम्बडा को "लैम्ब्डा प्रॉक्सी इंटीग्रेशन" के रूप में उपयोग कर सकते हैं , इसे [ https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda पर जाएं। html # एपि-गेटवे-प्रॉक्सी-इंटीग्रेशन-लैम्डा-फंक्शन-पाइथन] , इस लैम्ब्डा के विकल्प उपलब्ध हैं

Nodejs लैंबडा 'event.headers', 'event.pathParameters', 'event.body', 'event.stageVariables', और 'event.requestContext' के लिए

पायथन लैंबडा घटना के लिए ['हेडर'] ['पैराडर्नाम'] और इसी तरह



-1

इनमें से कई उत्तरों को पढ़ने के बाद, मैंने अजगर 201 के लिए लैंबडा के माध्यम से क्वेरी स्ट्रिंग परम को पुनः प्राप्त करने के लिए 2018 के अगस्त में कई के संयोजन का उपयोग किया।

सबसे पहले, मैं एपीआई गेटवे -> माय एपीआई -> संसाधनों (बाईं ओर) -> एकीकरण अनुरोध पर गया। सबसे नीचे, सामग्री प्रकार दर्ज करने के लिए मैपिंग टेम्प्लेट चुनेंapplication/json

इसके बाद, अमेज़ॅन द्वारा प्रदान की जाने वाली विधि अनुरोध पाश्चर टेम्पलेट का चयन करें और अपने एपीआई को बचाने और तैनात करने का चयन करें।

फिर, लैम्ब्डा event['params']है कि आप अपने सभी मापदंडों का उपयोग कैसे करते हैं। क्वेरी स्ट्रिंग के लिए:event['params']['querystring']

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.