All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.threadly.litesockets.protocols.http.shared.HTTPUtils Maven / Gradle / Ivy

package org.threadly.litesockets.protocols.http.shared;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.threadly.litesockets.buffers.MergedByteBuffers;
import org.threadly.litesockets.buffers.ReuseableMergedByteBuffers;
import org.threadly.litesockets.buffers.SimpleMergedByteBuffers;
import org.threadly.util.StringUtils;

/**
 * Utility functions for working with the HTTP protocol.
 */
public class HTTPUtils {
  private static final ByteBuffer EMPTY_CHUNK_BUFFER;
  
  static {
    byte[] size = chunkSizeBytes(0);
    ByteBuffer newBB = 
        ByteBuffer.allocate(size.length + HTTPConstants.HTTP_NEWLINE_DELIMINATOR.length() + 
                              HTTPConstants.HTTP_NEWLINE_DELIMINATOR.length());
    newBB.put(size);
    newBB.put(HTTPConstants.HTTP_NEWLINE_DELIMINATOR.getBytes());
    newBB.put(HTTPConstants.HTTP_NEWLINE_DELIMINATOR.getBytes());
    newBB.flip();
    EMPTY_CHUNK_BUFFER = newBB.asReadOnlyBuffer();
  }
  
  /**
   * Trim whitespace from the left side of the String only.
   * 
   * @param value String to trim from
   * @return A left-trimmed string
   */
  public static String leftTrim(String value) {
    int count = 0;
    while(value.length() > count && Character.isWhitespace(value.charAt(count))) {
      count++;
    }
    return value.substring(count);
  }
  
  /**
   * Used for parsing a chunk encoded request / response.  This will find the end of the chunk 
   * and then parse out the size of the next chunk
   * 
   * @param bb Source {@link ByteBuffer} to read from
   * @return The next chunk size or {@code -1} if could not be found or failed to parse
   */
  public static int getNextChunkLength(final ByteBuffer bb) {
    MergedByteBuffers mbb = new ReuseableMergedByteBuffers();
    mbb.add(bb);
    int pos = mbb.indexOf(HTTPConstants.HTTP_NEWLINE_DELIMINATOR);
    try {
      if(pos >= 0) {
        String csize = mbb.getAsString(pos);
        return Integer.parseInt(csize, HTTPConstants.HEX_SIZE);
      }
    } catch(NumberFormatException e) {
      return -1;
    }
    return -1;
  }
  
  /**
   * Wraps the given data in a chunk to be used for chunked encoding.
   * 
   * @param bb The data to wrap
   * @return A new buffer which wraps the data in a chunk encoded segment
   */
  public static MergedByteBuffers wrapInChunk(ByteBuffer bb) {
    if (bb != null && bb.hasRemaining()) {
      return new SimpleMergedByteBuffers(true, 
                                         ByteBuffer.wrap(chunkSizeBytes(bb.remaining())), 
                                         HTTPConstants.HTTP_NEWLINE_DELIMINATOR_BUFFER.duplicate(), 
                                         bb, 
                                         HTTPConstants.HTTP_NEWLINE_DELIMINATOR_BUFFER.duplicate());
    } else {
      return new SimpleMergedByteBuffers(true, EMPTY_CHUNK_BUFFER.duplicate());
    }
  }
  
  private static byte[] chunkSizeBytes(int size) {
    return Integer.toHexString(size).getBytes();
  }
  
  /**
   * Converts query parameters stored in a map to a {@link String} that can be added to the URI.
   * 
   * @param map Map to source the values from
   * @return HTTP standard query parameters, prefixed with {@code ?}
   */
  public static String queryToString(Map> map) {
    if(map.isEmpty()) {
      return "";
    }
    
    StringBuilder sb = new StringBuilder();
    sb.append('?');
    for(Map.Entry> e : map.entrySet()) {
      if (e.getValue() == null || e.getValue().isEmpty()) {
        if(sb.length() > 1) {
          sb.append('&');  
        }
        sb.append(e.getKey());
      } else {
        for (String v : e.getValue()) {
          if(sb.length() > 1) {
            sb.append('&');  
          }
          sb.append(e.getKey());
          if(! StringUtils.isNullOrEmpty(v)) {
            sb.append('=');
            sb.append(v);
          }
        }
      }
    }
    return sb.toString();
  }
  
  /**
   * Parses http standard query parameters from the URL into a {@link Map} representation.
   * 
   * @param query The String to parse
   * @return The parsed our parameters into a {@link Map}
   */
  public static Map> queryToMap(String query) {
    if (StringUtils.isNullOrEmpty(query)) {
      return Collections.emptyMap();
    }
    Map> map = new HashMap<>();
    if(query.startsWith("?")) {
      query = query.substring(1);
    }
    int qpos = query.indexOf("?");
    if (qpos >= 0){
      query = query.substring(qpos+1);
    }
    String[] tmpQ = query.trim().split("&");
    for(String kv: tmpQ) {
      String[] tmpkv = kv.split("=");
      if (tmpkv.length == 0) {
        // case where either no `=` or empty key string
        continue;
      }
      List paramValues = map.computeIfAbsent(tmpkv[0], (ignored) -> new ArrayList<>(2));
      if(tmpkv.length == 1) {
        paramValues.add("");
      } else {
        paramValues.add(tmpkv[1].trim());
      }
    }
    return Collections.unmodifiableMap(map);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy