HttpConfigs.java

  1. /**
  2.  * Copyright (C) 2017 HttpBuilder-NG Project
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *         http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16. package groovyx.net.http;

  17. import groovyx.net.http.optional.Csv;
  18. import groovyx.net.http.optional.Html;

  19. import java.io.IOException;
  20. import java.net.HttpCookie;
  21. import java.net.URI;
  22. import java.net.URISyntaxException;
  23. import java.net.URL;
  24. import java.nio.charset.Charset;
  25. import java.nio.charset.StandardCharsets;
  26. import java.time.Instant;
  27. import java.time.LocalDateTime;
  28. import java.time.ZoneId;
  29. import java.util.AbstractMap;
  30. import java.util.ArrayList;
  31. import java.util.Date;
  32. import java.util.LinkedHashMap;
  33. import java.util.List;
  34. import java.util.Map;
  35. import java.util.concurrent.ConcurrentHashMap;
  36. import java.util.concurrent.ConcurrentMap;
  37. import java.util.concurrent.CopyOnWriteArrayList;
  38. import java.util.function.BiConsumer;
  39. import java.util.function.BiFunction;
  40. import java.util.function.Function;

  41. import static groovyx.net.http.ChainedHttpConfig.Auth;
  42. import static groovyx.net.http.ChainedHttpConfig.AuthType;
  43. import static groovyx.net.http.ChainedHttpConfig.ChainedRequest;
  44. import static groovyx.net.http.ChainedHttpConfig.ChainedResponse;
  45. import static groovyx.net.http.ContentTypes.BINARY;
  46. import static groovyx.net.http.ContentTypes.JSON;
  47. import static groovyx.net.http.ContentTypes.TEXT;
  48. import static groovyx.net.http.ContentTypes.URLENC;
  49. import static groovyx.net.http.ContentTypes.XML;
  50. import static groovyx.net.http.Safe.ifClassIsLoaded;
  51. import static groovyx.net.http.Safe.register;

  52. public class HttpConfigs {

  53.     public static class BasicAuth implements Auth {
  54.         private String user;
  55.         private String password;
  56.         private boolean preemptive;
  57.         private AuthType authType;

  58.         public void basic(final String user, final String password, final boolean preemptive) {
  59.             this.user = user;
  60.             this.password = password;
  61.             this.preemptive = preemptive;
  62.             this.authType = AuthType.BASIC;
  63.         }

  64.         public void digest(final String user, final String password, final boolean preemptive) {
  65.             basic(user, password, preemptive);
  66.             this.authType = AuthType.DIGEST;
  67.         }

  68.         public String getUser() {
  69.             return user;
  70.         }

  71.         public String getPassword() {
  72.             return password;
  73.         }

  74.         public AuthType getAuthType() {
  75.             return authType;
  76.         }
  77.     }

  78.     public static class ThreadSafeAuth implements Auth {
  79.         volatile String user;
  80.         volatile String password;
  81.         volatile boolean preemptive;
  82.         volatile AuthType authType;

  83.         public ThreadSafeAuth() { }

  84.         public ThreadSafeAuth(final BasicAuth toCopy) {
  85.             this.user = toCopy.user;
  86.             this.password = toCopy.password;
  87.             this.preemptive = toCopy.preemptive;
  88.             this.authType = toCopy.authType;
  89.         }

  90.         public void basic(final String user, final String password, final boolean preemptive) {
  91.             this.user = user;
  92.             this.password = password;
  93.             this.preemptive = preemptive;
  94.             this.authType = AuthType.BASIC;
  95.         }

  96.         public void digest(final String user, final String password, final boolean preemptive) {
  97.             basic(user, password, preemptive);
  98.             this.authType = AuthType.DIGEST;
  99.         }

  100.         public String getUser() {
  101.             return user;
  102.         }

  103.         public String getPassword() {
  104.             return password;
  105.         }

  106.         public AuthType getAuthType() {
  107.             return authType;
  108.         }
  109.     }

  110.     public static abstract class BaseRequest implements ChainedRequest {

  111.         final ChainedRequest parent;
  112.         HttpVerb verb;

  113.         public BaseRequest(final ChainedRequest parent) {
  114.             this.parent = parent;
  115.         }

  116.         public ChainedRequest getParent() {
  117.             return parent;
  118.         }

  119.         public void setCharset(final String val) {
  120.             setCharset(Charset.forName(val));
  121.         }

  122.         public void setUri(final String val) {
  123.             getUri().setFull(val);
  124.         }

  125.         public void setRaw(final String val){
  126.             UriBuilder uriBuilder = getUri();
  127.             uriBuilder.setUseRawValues(true);
  128.             uriBuilder.setFull(val);
  129.         }

  130.         public void setUri(final URI val) {
  131.             getUri().setFull(val);
  132.         }

  133.         public void setUri(final URL val) throws URISyntaxException {
  134.             getUri().setFull(val.toURI());
  135.         }

  136.         public BiConsumer<ChainedHttpConfig,ToServer> encoder(final String contentType) {
  137.             final BiConsumer<ChainedHttpConfig,ToServer> enc =  getEncoderMap().get(contentType);
  138.             return enc != null ? enc : null;
  139.         }

  140.         public void encoder(final String contentType, final BiConsumer<ChainedHttpConfig,ToServer> val) {
  141.             getEncoderMap().put(contentType, val);
  142.         }

  143.         public void encoder(final Iterable<String> contentTypes, final BiConsumer<ChainedHttpConfig,ToServer> val) {
  144.             for(String contentType : contentTypes) {
  145.                 encoder(contentType, val);
  146.             }
  147.         }

  148.         public void setAccept(final String[] values) {
  149.             getHeaders().put("Accept", String.join(";", values));
  150.         }

  151.         public void setAccept(final Iterable<String> values) {
  152.             getHeaders().put("Accept", String.join(";", values));
  153.         }

  154.         public void setHeaders(final Map<String,CharSequence> toAdd) {
  155.             final Map<String,CharSequence> h = getHeaders();
  156.             if(toAdd != null){
  157.                 for(final Map.Entry<String,CharSequence> entry : toAdd.entrySet()) {
  158.                     h.put(entry.getKey(), entry.getValue());
  159.                 }
  160.             }
  161.         }

  162.         public void cookie(final String name, final String value, final Instant instant) {
  163.             final HttpCookie cookie = new HttpCookie(name, value);
  164.             cookie.setPath("/");
  165.             final Instant now = Instant.now();
  166.             if(instant != null && now.isBefore(instant)) {
  167.                 cookie.setMaxAge(instant.getEpochSecond() - now.getEpochSecond());
  168.             }

  169.             getCookies().add(cookie);
  170.         }

  171.         public void cookie(final String name, final String value, final Date date) {
  172.             cookie(name, value, date == null ? (Instant) null : date.toInstant());
  173.         }

  174.         public void cookie(final String name, final String value, final LocalDateTime dateTime){
  175.             cookie(name, value, dateTime == null ? (Instant) null : dateTime.atZone(ZoneId.systemDefault()).toInstant());
  176.         }

  177.         @Override
  178.         public HttpVerb getVerb() {
  179.             return verb;
  180.         }

  181.         @Override
  182.         public void setVerb(final HttpVerb verb) {
  183.             this.verb = verb;
  184.         }
  185.     }

  186.     public static class BasicRequest extends BaseRequest {
  187.         private String contentType;
  188.         private Charset charset;
  189.         private UriBuilder uriBuilder;
  190.         private final Map<String, CharSequence> headers = new LinkedHashMap<>();
  191.         private Object body;
  192.         private final Map<String,BiConsumer<ChainedHttpConfig,ToServer>> encoderMap = new LinkedHashMap<>();
  193.         private BasicAuth auth = new BasicAuth();
  194.         private List<HttpCookie> cookies = new ArrayList<>(1);

  195.         protected BasicRequest(ChainedRequest parent) {
  196.             super(parent);
  197.             this.uriBuilder = (parent == null) ? UriBuilder.basic(null) : UriBuilder.basic(parent.getUri());
  198.         }

  199.         public Map<String,BiConsumer<ChainedHttpConfig,ToServer>> getEncoderMap() {
  200.             return encoderMap;
  201.         }

  202.         public List<HttpCookie> getCookies() {
  203.             return cookies;
  204.         }

  205.         public String getContentType() {
  206.             return contentType;
  207.         }

  208.         public void setContentType(final String val) {
  209.             this.contentType = val;
  210.         }

  211.         public void setCharset(final Charset val) {
  212.             this.charset = val;
  213.         }

  214.         public Charset getCharset() {
  215.             return charset;
  216.         }

  217.         public UriBuilder getUri() {
  218.             return uriBuilder;
  219.         }

  220.         public Map<String,CharSequence> getHeaders() {
  221.             return headers;
  222.         }

  223.         public Object getBody() {
  224.             return body;
  225.         }

  226.         public void setBody(Object val) {
  227.             this.body = val;
  228.         }

  229.         public BasicAuth getAuth() {
  230.             return auth;
  231.         }
  232.     }

  233.     public static class ThreadSafeRequest extends BaseRequest {

  234.         private volatile String contentType;
  235.         private volatile Charset charset;
  236.         private volatile UriBuilder uriBuilder;
  237.         private final ConcurrentMap<String,CharSequence> headers = new ConcurrentHashMap<>();
  238.         private volatile Object body;
  239.         private final ConcurrentMap<String,BiConsumer<ChainedHttpConfig,ToServer>> encoderMap = new ConcurrentHashMap<>();
  240.         private final ThreadSafeAuth auth;
  241.         private final List<HttpCookie> cookies = new CopyOnWriteArrayList<>();

  242.         public ThreadSafeRequest(final ChainedRequest parent) {
  243.             super(parent);
  244.             this.auth = new ThreadSafeAuth();
  245.             this.uriBuilder = (parent == null) ? UriBuilder.threadSafe(null) : UriBuilder.threadSafe(parent.getUri());
  246.         }

  247.         public List<HttpCookie> getCookies() {
  248.             return cookies;
  249.         }

  250.         public Map<String,BiConsumer<ChainedHttpConfig,ToServer>> getEncoderMap() {
  251.             return encoderMap;
  252.         }

  253.         public String getContentType() {
  254.             return contentType;
  255.         }

  256.         public void setContentType(final String val) {
  257.             this.contentType = val;
  258.         }

  259.         public Charset getCharset() {
  260.             return charset;
  261.         }

  262.         public void setCharset(final Charset val) {
  263.             this.charset = val;
  264.         }

  265.         public UriBuilder getUri() {
  266.             return uriBuilder;
  267.         }

  268.         public Map<String,CharSequence> getHeaders() {
  269.             return headers;
  270.         }

  271.         public Object getBody() {
  272.             return body;
  273.         }

  274.         public void setBody(Object val) {
  275.             this.body = val;
  276.         }

  277.         public ThreadSafeAuth getAuth() {
  278.             return auth;
  279.         }
  280.     }

  281.     public static abstract class BaseResponse implements ChainedResponse {

  282.         abstract protected Map<Integer,BiFunction<FromServer, Object, ?>> getByCode();
  283.         abstract protected BiFunction<FromServer, Object, ?> getSuccess();
  284.         abstract protected BiFunction<FromServer, Object, ?> getFailure();
  285.         abstract protected Map<String,BiFunction<ChainedHttpConfig,FromServer,Object>> getParserMap();

  286.         private final ChainedResponse parent;

  287.         public ChainedResponse getParent() {
  288.             return parent;
  289.         }

  290.         protected BaseResponse(final ChainedResponse parent) {
  291.             this.parent = parent;
  292.         }

  293.         public void when(String code, BiFunction<FromServer, Object, ?> closure) {
  294.             when(Integer.valueOf(code), closure);
  295.         }

  296.         public void when(Integer code, BiFunction<FromServer, Object, ?> closure) {
  297.             getByCode().put(code, closure);
  298.         }

  299.         public void when(final HttpConfig.Status status, final BiFunction<FromServer, Object, ?> closure) {
  300.             if(status == HttpConfig.Status.SUCCESS) {
  301.                 success(closure);
  302.             } else {
  303.                 failure(closure);
  304.             }
  305.         }

  306.         public BiFunction<FromServer, Object, ?> when(final Integer code) {
  307.             if(getByCode().containsKey(code)) {
  308.                 return getByCode().get(code);
  309.             }

  310.             if(code < 400 && getSuccess() != null) {
  311.                 return getSuccess();
  312.             }

  313.             if(code >= 400 && getFailure() != null) {
  314.                 return getFailure();
  315.             }

  316.             return null;
  317.         }

  318.         public BiFunction<ChainedHttpConfig,FromServer,Object> parser(final String contentType) {
  319.             final BiFunction<ChainedHttpConfig,FromServer,Object> p = getParserMap().get(contentType);
  320.             return p != null ? p : null;
  321.         }

  322.         public void parser(final String contentType, BiFunction<ChainedHttpConfig,FromServer,Object> val) {
  323.             getParserMap().put(contentType, val);
  324.         }

  325.         public void parser(final Iterable<String> contentTypes, BiFunction<ChainedHttpConfig,FromServer,Object> val) {
  326.             for(String contentType : contentTypes) {
  327.                 parser(contentType, val);
  328.             }
  329.         }

  330.         public abstract void setType(Class<?> type);
  331.     }

  332.     public static class BasicResponse extends BaseResponse {

  333.         private final Map<Integer,BiFunction<FromServer, Object, ?>> byCode = new LinkedHashMap<>();
  334.         private BiFunction<FromServer, Object, ?> successHandler;
  335.         private BiFunction<FromServer, Object, ?> failureHandler;
  336.         private Function<Throwable,?> exceptionHandler;
  337.         private final Map<String,BiFunction<ChainedHttpConfig,FromServer,Object>> parserMap = new LinkedHashMap<>();
  338.         private Class<?> type = Object.class;

  339.         protected BasicResponse(final ChainedResponse parent) {
  340.             super(parent);
  341.         }

  342.         public Map<String,BiFunction<ChainedHttpConfig,FromServer,Object>> getParserMap() {
  343.             return parserMap;
  344.         }

  345.         protected Map<Integer,BiFunction<FromServer, Object, ?>> getByCode() {
  346.             return byCode;
  347.         }

  348.         protected BiFunction<FromServer, Object, ?> getSuccess() {
  349.             return successHandler;
  350.         }

  351.         protected BiFunction<FromServer, Object, ?> getFailure() {
  352.             return failureHandler;
  353.         }

  354.         public Function<Throwable,?> getException() {
  355.             return exceptionHandler;
  356.         }

  357.         public void success(final BiFunction<FromServer, Object, ?> val) {
  358.             successHandler = val;
  359.         }

  360.         public void failure(final BiFunction<FromServer, Object, ?> val) {
  361.             failureHandler = val;
  362.         }

  363.         public void exception(final Function<Throwable,?> val) {
  364.             exceptionHandler = val;
  365.         }

  366.         public Class<?> getType() {
  367.             return type;
  368.         }

  369.         public void setType(Class<?> val) {
  370.             type = val;
  371.         }
  372.     }

  373.     public static class ThreadSafeResponse extends BaseResponse {

  374.         private final ConcurrentMap<String,BiFunction<ChainedHttpConfig,FromServer,Object>> parserMap = new ConcurrentHashMap<>();
  375.         private final ConcurrentMap<Integer,BiFunction<FromServer, Object, ?>> byCode = new ConcurrentHashMap<>();
  376.         private volatile BiFunction<FromServer, Object, ?> successHandler;
  377.         private volatile BiFunction<FromServer, Object, ?> failureHandler;
  378.         private volatile Function<Throwable,?> exceptionHandler;
  379.         private volatile Class<?> type = Object.class;

  380.         public ThreadSafeResponse(final ChainedResponse parent) {
  381.             super(parent);
  382.         }

  383.         protected Map<String,BiFunction<ChainedHttpConfig,FromServer,Object>> getParserMap() {
  384.             return parserMap;
  385.         }

  386.         protected Map<Integer,BiFunction<FromServer, Object, ?>> getByCode() {
  387.             return byCode;
  388.         }

  389.         protected BiFunction<FromServer, Object, ?> getSuccess() {
  390.             return successHandler;
  391.         }

  392.         protected BiFunction<FromServer, Object, ?> getFailure() {
  393.             return failureHandler;
  394.         }

  395.         public Function<Throwable,?> getException() {
  396.             return exceptionHandler;
  397.         }

  398.         public void success(final BiFunction<FromServer, Object, ?> val) {
  399.             successHandler = val;
  400.         }

  401.         public void failure(final BiFunction<FromServer, Object, ?> val) {
  402.             failureHandler = val;
  403.         }

  404.         public void exception(final Function<Throwable,?> val) {
  405.             this.exceptionHandler = val;
  406.         }

  407.         public Class<?> getType() {
  408.             return type;
  409.         }

  410.         public void setType(final Class<?> val) {
  411.             type = val;
  412.         }
  413.     }

  414.     public abstract static class BaseHttpConfig implements ChainedHttpConfig {

  415.         private final ChainedHttpConfig parent;

  416.         public BaseHttpConfig(ChainedHttpConfig parent) {
  417.             this.parent = parent;
  418.         }

  419.         public ChainedHttpConfig getParent() {
  420.             return parent;
  421.         }

  422.         public ChainedHttpConfig configure() {
  423.             getRequest().setCharset(StandardCharsets.UTF_8);
  424.             getRequest().encoder(BINARY, NativeHandlers.Encoders::binary);
  425.             getRequest().encoder(TEXT, (f,s) -> {
  426.                     try {
  427.                         NativeHandlers.Encoders.text(f, s);
  428.                     }
  429.                     catch(IOException e) {
  430.                         throw new TransportingException(e);
  431.                     } });

  432.             getRequest().encoder(URLENC, NativeHandlers.Encoders::form);
  433.             getRequest().encoder(XML, NativeHandlers.Encoders::xml);
  434.             getRequest().encoder(JSON, NativeHandlers.Encoders::json);

  435.             getResponse().success(NativeHandlers::success);
  436.             getResponse().failure(NativeHandlers::failure);
  437.             getResponse().exception(NativeHandlers::exception);

  438.             getResponse().parser(BINARY, NativeHandlers.Parsers::streamToBytes);
  439.             getResponse().parser(TEXT, NativeHandlers.Parsers::textToString);
  440.             getResponse().parser(URLENC, NativeHandlers.Parsers::form);
  441.             getResponse().parser(XML, NativeHandlers.Parsers::xml);
  442.             getResponse().parser(JSON, NativeHandlers.Parsers::json);

  443.             return this;
  444.         }

  445.         public void context(final String contentType, final Object id, final Object obj) {
  446.             getContextMap().put(new AbstractMap.SimpleImmutableEntry<>(contentType, id), obj);
  447.         }
  448.     }

  449.     public static class ThreadSafeHttpConfig extends BaseHttpConfig {
  450.         private final ThreadSafeRequest request;
  451.         private final ThreadSafeResponse response;
  452.         private final ConcurrentMap<Map.Entry<String,Object>,Object> contextMap = new ConcurrentHashMap<>();

  453.         public ThreadSafeHttpConfig(final ChainedHttpConfig parent) {
  454.             super(parent);
  455.             if(parent == null) {
  456.                 this.request = new ThreadSafeRequest(null);
  457.                 this.response = new ThreadSafeResponse(null);
  458.             }
  459.             else {
  460.                 this.request = new ThreadSafeRequest(parent.getChainedRequest());
  461.                 this.response = new ThreadSafeResponse(parent.getChainedResponse());
  462.             }
  463.         }

  464.         public Request getRequest() {
  465.             return request;
  466.         }

  467.         public Response getResponse() {
  468.             return response;
  469.         }

  470.         public ChainedRequest getChainedRequest() {
  471.             return request;
  472.         }

  473.         public ChainedResponse getChainedResponse() {
  474.             return response;
  475.         }

  476.         public Map<Map.Entry<String,Object>,Object> getContextMap() {
  477.             return contextMap;
  478.         }
  479.     }

  480.     public static class BasicHttpConfig extends BaseHttpConfig {
  481.         private final BasicRequest request;
  482.         private final BasicResponse response;
  483.         private final Map<Map.Entry<String,Object>,Object> contextMap = new LinkedHashMap<>(1);

  484.         public BasicHttpConfig(final ChainedHttpConfig parent) {
  485.             super(parent);
  486.             if(parent == null) {
  487.                 this.request = new BasicRequest(null);
  488.                 this.response = new BasicResponse(null);
  489.             }
  490.             else {
  491.                 this.request = new BasicRequest(parent.getChainedRequest());
  492.                 this.response = new BasicResponse(parent.getChainedResponse());
  493.             }
  494.         }

  495.         public BasicRequest getRequest() {
  496.             return request;
  497.         }

  498.         public BasicResponse getResponse() {
  499.             return response;
  500.         }

  501.         public BasicRequest getChainedRequest() {
  502.             return request;
  503.         }

  504.         public BasicResponse getChainedResponse() {
  505.             return response;
  506.         }

  507.         public Map<Map.Entry<String,Object>,Object> getContextMap() {
  508.             return contextMap;
  509.         }
  510.     }

  511.     private static final ThreadSafeHttpConfig root;

  512.     static {
  513.         root = (ThreadSafeHttpConfig) new ThreadSafeHttpConfig(null).configure();

  514.         register(root, ifClassIsLoaded("org.cyberneko.html.parsers.SAXParser"),
  515.                  "text/html", () -> NativeHandlers.Encoders::xml, Html.neckoParserSupplier);

  516.         register(root, ifClassIsLoaded("org.jsoup.Jsoup"),
  517.                  "text/html", Html.jsoupEncoderSupplier, Html.jsoupParserSupplier);

  518.         if(register(root, ifClassIsLoaded("com.opencsv.CSVReader"),
  519.                     "text/csv", Csv.encoderSupplier, Csv.parserSupplier)) {
  520.             root.context("text/csv", Csv.Context.ID, Csv.Context.DEFAULT_CSV);
  521.         }

  522.         if(register(root, ifClassIsLoaded("com.opencsv.CSVReader"),
  523.                     "text/tab-separated-values", Csv.encoderSupplier, Csv.parserSupplier)) {
  524.             root.context("text/tab-separated-values", Csv.Context.ID, Csv.Context.DEFAULT_TSV);
  525.         }
  526.     }

  527.     public static ChainedHttpConfig root() {
  528.         return root;
  529.     }

  530.     public static ChainedHttpConfig threadSafe(final ChainedHttpConfig parent) {
  531.         return new ThreadSafeHttpConfig(parent);
  532.     }

  533.     public static ChainedHttpConfig classLevel(final boolean threadSafe) {
  534.         return threadSafe ? threadSafe(root) : basic(root);
  535.     }

  536.     public static ChainedHttpConfig basic(final ChainedHttpConfig parent) {
  537.         return new BasicHttpConfig(parent);
  538.     }

  539.     public static BasicHttpConfig requestLevel(final ChainedHttpConfig parent) {
  540.         return new BasicHttpConfig(parent);
  541.     }
  542. }