| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.dubbo.rpc.protocol.rest.extension.resteasy.filter; |
| |
| import org.jboss.resteasy.core.interception.jaxrs.SuspendableContainerRequestContext; |
| import org.jboss.resteasy.plugins.server.netty.NettyHttpRequest; |
| import org.jboss.resteasy.specimpl.BuiltResponse; |
| import org.jboss.resteasy.specimpl.ResteasyHttpHeaders; |
| import org.jboss.resteasy.spi.ApplicationException; |
| import org.jboss.resteasy.spi.ResteasyProviderFactory; |
| |
| import javax.ws.rs.container.ContainerRequestFilter; |
| import javax.ws.rs.core.Cookie; |
| import javax.ws.rs.core.MediaType; |
| import javax.ws.rs.core.MultivaluedMap; |
| import javax.ws.rs.core.Request; |
| import javax.ws.rs.core.Response; |
| import javax.ws.rs.core.SecurityContext; |
| import javax.ws.rs.core.UriInfo; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.function.Supplier; |
| |
| public class DubboPreMatchContainerRequestContext implements SuspendableContainerRequestContext { |
| protected final NettyHttpRequest httpRequest; |
| protected Response response; |
| private ContainerRequestFilter[] requestFilters; |
| private int currentFilter; |
| private boolean suspended; |
| private boolean filterReturnIsMeaningful = true; |
| private Supplier<BuiltResponse> continuation; |
| private Map<Class<?>, Object> contextDataMap; |
| private boolean inFilter; |
| private Throwable throwable; |
| private boolean startedContinuation; |
| |
| public DubboPreMatchContainerRequestContext( |
| final NettyHttpRequest request, |
| final ContainerRequestFilter[] requestFilters, |
| final Supplier<BuiltResponse> continuation) { |
| this.httpRequest = request; |
| this.requestFilters = requestFilters; |
| this.continuation = continuation; |
| contextDataMap = ResteasyProviderFactory.getContextDataMap(); |
| } |
| |
| public NettyHttpRequest getHttpRequest() { |
| return httpRequest; |
| } |
| |
| public Response getResponseAbortedWith() { |
| return response; |
| } |
| |
| @Override |
| public Object getProperty(String name) { |
| return httpRequest.getAttribute(name); |
| } |
| |
| @Override |
| public Collection<String> getPropertyNames() { |
| ArrayList<String> names = new ArrayList<>(); |
| Enumeration<String> enames = httpRequest.getAttributeNames(); |
| while (enames.hasMoreElements()) { |
| names.add(enames.nextElement()); |
| } |
| return names; |
| } |
| |
| @Override |
| public void setProperty(String name, Object object) { |
| httpRequest.setAttribute(name, object); |
| } |
| |
| @Override |
| public void removeProperty(String name) { |
| httpRequest.removeAttribute(name); |
| } |
| |
| @Override |
| public UriInfo getUriInfo() { |
| return httpRequest.getUri(); |
| } |
| |
| @Override |
| public void setRequestUri(URI requestUri) throws IllegalStateException { |
| httpRequest.setRequestUri(requestUri); |
| } |
| |
| @Override |
| public void setRequestUri(URI baseUri, URI requestUri) throws IllegalStateException { |
| httpRequest.setRequestUri(baseUri, requestUri); |
| } |
| |
| @Override |
| public String getMethod() { |
| return httpRequest.getHttpMethod(); |
| } |
| |
| @Override |
| public void setMethod(String method) { |
| httpRequest.setHttpMethod(method); |
| } |
| |
| @Override |
| public MultivaluedMap<String, String> getHeaders() { |
| return ((ResteasyHttpHeaders) httpRequest.getHttpHeaders()).getMutableHeaders(); |
| } |
| |
| @Override |
| public Date getDate() { |
| return httpRequest.getHttpHeaders().getDate(); |
| } |
| |
| @Override |
| public Locale getLanguage() { |
| return httpRequest.getHttpHeaders().getLanguage(); |
| } |
| |
| @Override |
| public int getLength() { |
| return httpRequest.getHttpHeaders().getLength(); |
| } |
| |
| @Override |
| public MediaType getMediaType() { |
| return httpRequest.getHttpHeaders().getMediaType(); |
| } |
| |
| @Override |
| public List<MediaType> getAcceptableMediaTypes() { |
| return httpRequest.getHttpHeaders().getAcceptableMediaTypes(); |
| } |
| |
| @Override |
| public List<Locale> getAcceptableLanguages() { |
| return httpRequest.getHttpHeaders().getAcceptableLanguages(); |
| } |
| |
| @Override |
| public Map<String, Cookie> getCookies() { |
| return httpRequest.getHttpHeaders().getCookies(); |
| } |
| |
| @Override |
| public boolean hasEntity() { |
| return getMediaType() != null; |
| } |
| |
| @Override |
| public InputStream getEntityStream() { |
| return httpRequest.getInputStream(); |
| } |
| |
| @Override |
| public void setEntityStream(InputStream entityStream) { |
| httpRequest.setInputStream(entityStream); |
| } |
| |
| @Override |
| public SecurityContext getSecurityContext() { |
| return ResteasyProviderFactory.getContextData(SecurityContext.class); |
| } |
| |
| @Override |
| public void setSecurityContext(SecurityContext context) { |
| ResteasyProviderFactory.pushContext(SecurityContext.class, context); |
| } |
| |
| @Override |
| public Request getRequest() { |
| return ResteasyProviderFactory.getContextData(Request.class); |
| } |
| |
| @Override |
| public String getHeaderString(String name) { |
| return httpRequest.getHttpHeaders().getHeaderString(name); |
| } |
| |
| @Override |
| public synchronized void suspend() { |
| if (continuation == null) throw new RuntimeException("Suspend not supported yet"); |
| suspended = true; |
| } |
| |
| @Override |
| public synchronized void abortWith(Response response) { |
| if (suspended && !inFilter) { |
| try (ResteasyProviderFactory.CloseableContext c = |
| ResteasyProviderFactory.addCloseableContextDataLevel(contextDataMap)) { |
| httpRequest.getAsyncContext().getAsyncResponse().resume(response); |
| } |
| } else { |
| // not suspended, or suspend/abortWith within filter, same thread: collect and move on |
| this.response = response; |
| suspended = false; |
| } |
| } |
| |
| @Override |
| public synchronized void resume() { |
| if (!suspended) throw new RuntimeException("Cannot resume: not suspended"); |
| if (inFilter) { |
| // suspend/resume within filter, same thread: just ignore and move on |
| suspended = false; |
| return; |
| } |
| |
| // go on, but with proper exception handling |
| try (ResteasyProviderFactory.CloseableContext c = |
| ResteasyProviderFactory.addCloseableContextDataLevel(contextDataMap)) { |
| filter(); |
| } catch (Throwable t) { |
| // don't throw to client |
| writeException(t); |
| } |
| } |
| |
| @Override |
| public synchronized void resume(Throwable t) { |
| if (!suspended) throw new RuntimeException("Cannot resume: not suspended"); |
| if (inFilter) { |
| // not suspended, or suspend/abortWith within filter, same thread: collect and move on |
| throwable = t; |
| suspended = false; |
| } else { |
| try (ResteasyProviderFactory.CloseableContext c = |
| ResteasyProviderFactory.addCloseableContextDataLevel(contextDataMap)) { |
| writeException(t); |
| } |
| } |
| } |
| |
| private void writeException(Throwable t) { |
| /* |
| * Here, contrary to ContainerResponseContextImpl.writeException, we can use the async response |
| * to write the exception, because it calls the right response filters, complete() and callbacks |
| */ |
| httpRequest.getAsyncContext().getAsyncResponse().resume(t); |
| } |
| |
| public synchronized BuiltResponse filter() throws Throwable { |
| while (currentFilter < requestFilters.length) { |
| ContainerRequestFilter filter = requestFilters[currentFilter++]; |
| try { |
| suspended = false; |
| response = null; |
| throwable = null; |
| inFilter = true; |
| filter.filter(this); |
| } catch (IOException e) { |
| throw new ApplicationException(e); |
| } finally { |
| inFilter = false; |
| } |
| if (suspended) { |
| if (!httpRequest.getAsyncContext().isSuspended()) |
| // ignore any abort request until we are resumed |
| filterReturnIsMeaningful = false; |
| response = null; |
| return null; |
| } |
| BuiltResponse serverResponse = (BuiltResponse) getResponseAbortedWith(); |
| if (serverResponse != null) { |
| // handle the case where we've been suspended by a previous filter |
| return serverResponse; |
| } |
| |
| if (throwable != null) { |
| // handle the case where we've been suspended by a previous filter |
| throw throwable; |
| } |
| } |
| // here it means we reached the last filter |
| // some frameworks don't support async request filters, in which case suspend() is forbidden |
| // so if we get here we're still synchronous and don't have a continuation, which must be in |
| // the caller |
| startedContinuation = true; |
| if (continuation == null) return null; |
| // in any case, return the continuation: sync will use it, and async will ignore it |
| return continuation.get(); |
| } |
| |
| public boolean startedContinuation() { |
| return startedContinuation; |
| } |
| } |