web-dev-qa-db-ja.com

PUTまたはPOSTリクエストの未加工のボディにアクセスする

GrailsにRESTful APIを実装し、リクエストの本文に署名することを含むカスタム認証スキームを使用しています(AmazonのS3認証スキームと同様の方法で)。したがって、リクエストを認証するには、生のPOSTまたはPUT本文コンテンツにアクセスして、デジタル署名を計算および検証する必要があります。

コントローラのbeforeInterceptorで認証を行っています。したがって、インターセプターでrequest.bodyのようなものにアクセスできるようにして、実際のアクションでrequest.JSONを引き続き使用できるようにしたいと考えています。 getInputStreamまたはgetReader(ServletRequestによって提供されるメソッド)を使用してインターセプターで本文を読み取ると、request.JSONを介してアクセスしようとすると、アクションで本文が空に表示されます。

私はDjango=からGrailsに移行しており、Djangoで1年前にまったく同じ問題が発生しましたが、すぐにパッチが適用されました。Djangoは、この目的で使用できるrequest.raw_post_data属性を提供します。

最後に、ナイスでRESTfulになるために、これがPOSTおよびPUTリクエストで機能するようにします。

アドバイスやアドバイスをいただければ幸いです。それが存在しない場合、私は迅速で汚いハックのアイデアよりもエレガントなソリューションを実装する方法へのポインタを好むでしょう。 =)Djangoでは、いくつかのミドルウェアリクエストハンドラーを編集して、リクエストにいくつかのプロパティを追加しました。私はGroovyとGrailsに非常に慣れていないので、そのコードがどこにあるのかわかりませんが、必要に応じて同じことをしてもかまいません。

27
Mickey Ristroph

これは、サーブレットフィルターでHttpServletRequestをオーバーライドすることで可能です。

リクエストの本文を保存するHttpServletRequestWrapperを実装する必要があります:src/Java/grails/util/http/MultiReadHttpServletRequest.Java

package grails.util.http;

import org.Apache.commons.io.IOUtils;

import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletInputStream;
import Java.io.*;
import Java.util.concurrent.atomic.AtomicBoolean;

public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {

    private byte[] body;

    public MultiReadHttpServletRequest(HttpServletRequest httpServletRequest) {
        super(httpServletRequest);
        // Read the request body and save it as a byte array
        InputStream is = super.getInputStream();
        body = IOUtils.toByteArray(is);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new ServletInputStreamImpl(new ByteArrayInputStream(body));
    }

    @Override
    public BufferedReader getReader() throws IOException {
        String enc = getCharacterEncoding();
        if(enc == null) enc = "UTF-8";
        return new BufferedReader(new InputStreamReader(getInputStream(), enc));
    }

    private class ServletInputStreamImpl extends ServletInputStream {

        private InputStream is;

        public ServletInputStreamImpl(InputStream is) {
            this.is = is;
        }

        public int read() throws IOException {
            return is.read();
        }

        public boolean markSupported() {
            return false;
        }

        public synchronized void mark(int i) {
            throw new RuntimeException(new IOException("mark/reset not supported"));
        }

        public synchronized void reset() throws IOException {
            throw new IOException("mark/reset not supported");
        }
    }

}

現在のservletRequestをオーバーライドするサーブレットフィルター:src/Java/grails/util/http/MultiReadServletFilter.Java

package grails.util.http;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import Java.io.IOException;
import Java.util.Set;
import Java.util.TreeSet;

public class MultiReadServletFilter implements Filter {

    private static final Set<String> MULTI_READ_HTTP_METHODS = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER) {{
        // Enable Multi-Read for PUT and POST requests
            add("PUT");
            add("POST");
    }};

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if(servletRequest instanceof HttpServletRequest) {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            // Check wether the current request needs to be able to support the body to be read multiple times
            if(MULTI_READ_HTTP_METHODS.contains(request.getMethod())) {
                // Override current HttpServletRequest with custom implementation
                filterChain.doFilter(new MultiReadHttpServletRequest(request), servletResponse);
                return;
            }
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    public void init(FilterConfig filterConfig) throws ServletException {
    }

    public void destroy() {
    }
}

次に、grails install-templatesを実行し、src/templates/warのweb.xmlを編集して、charEncodingFilter定義の後にこれを追加する必要があります。

<filter>
    <filter-name>multireadFilter</filter-name>
    <filter-class>grails.util.http.MultiReadServletFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>multireadFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

これで、request.inputStreamを必要なだけ呼び出すことができるはずです。

私はこの具体的なコード/手順をテストしていませんが、過去に同様のことをしたので、うまくいくはずです;-)

注:巨大なリクエストはアプリケーションを停止する可能性があることに注意してください(OutOfMemory ...)

43

ここに見られるように

http://jira.codehaus.org/browse/GRAILS-2017

grailsのXMLの自動処理をオフにするだけで、コントローラーでテキストにアクセスできるようになります。このような

class EventsController {   

static allowedMethods = [add:'POST']

def add = {
    log.info("Got request " + request.reader.text)      
    render "OK"
}}

ベスト、アンダース

26
anders.norgaard

POSTリクエストのストリームとリクエストの両方のパラメーターへのアクセスを継続できる唯一の方法は、ストリームの読み取りとパラメーターアクセスをオーバーライドするラッパーを記述することです。ここでは良い例です:

HttpServletRequestの本文を変更

1
sean