001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.support.sentinel; 017 018import com.alibaba.csp.sentinel.util.StringUtil; 019import com.jfinal.kit.JsonKit; 020import com.jfinal.kit.LogKit; 021 022import javax.servlet.http.HttpServletRequest; 023import javax.servlet.http.HttpServletResponse; 024import java.io.IOException; 025import java.io.PrintWriter; 026import java.util.Map; 027 028/** 029 * @author michael yang (fuhai999@gmail.com) 030 * @Date: 2020/3/22 031 */ 032public class SentinelUtil { 033 034 private static final String PATH_SPLIT = "/"; 035 036 public static String buildResource(HttpServletRequest request) { 037 String pathInfo = getResourcePath(request); 038 if (!pathInfo.startsWith(PATH_SPLIT)) { 039 pathInfo = PATH_SPLIT + pathInfo; 040 } 041 042 if (PATH_SPLIT.equals(pathInfo)) { 043 return pathInfo; 044 } 045 046 // Note: pathInfo should be converted to camelCase style. 047 int lastSlashIndex = pathInfo.lastIndexOf("/"); 048 049 if (lastSlashIndex >= 0) { 050 pathInfo = pathInfo.substring(0, lastSlashIndex) + "/" 051 + StringUtil.trim(pathInfo.substring(lastSlashIndex + 1)); 052 } else { 053 pathInfo = PATH_SPLIT + StringUtil.trim(pathInfo); 054 } 055 056 return pathInfo; 057 } 058 059 060 private static String getResourcePath(HttpServletRequest request) { 061 String pathInfo = normalizeAbsolutePath(request.getPathInfo(), false); 062 String servletPath = normalizeAbsolutePath(request.getServletPath(), pathInfo.length() != 0); 063 064 return servletPath + pathInfo; 065 } 066 067 068 private static String normalizeAbsolutePath(String path, boolean removeTrailingSlash) throws IllegalStateException { 069 return normalizePath(path, true, false, removeTrailingSlash); 070 } 071 072 073 private static String normalizePath(String path, boolean forceAbsolute, boolean forceRelative, 074 boolean removeTrailingSlash) throws IllegalStateException { 075 char[] pathChars = StringUtil.trimToEmpty(path).toCharArray(); 076 int length = pathChars.length; 077 078 // Check path and slash. 079 boolean startsWithSlash = false; 080 boolean endsWithSlash = false; 081 082 if (length > 0) { 083 char firstChar = pathChars[0]; 084 char lastChar = pathChars[length - 1]; 085 086 startsWithSlash = firstChar == PATH_SPLIT.charAt(0) || firstChar == '\\'; 087 endsWithSlash = lastChar == PATH_SPLIT.charAt(0) || lastChar == '\\'; 088 } 089 090 StringBuilder buf = new StringBuilder(length); 091 boolean isAbsolutePath = forceAbsolute || !forceRelative && startsWithSlash; 092 int index = startsWithSlash ? 0 : -1; 093 int level = 0; 094 095 if (isAbsolutePath) { 096 buf.append(PATH_SPLIT); 097 } 098 099 while (index < length) { 100 index = indexOfSlash(pathChars, index + 1, false); 101 102 if (index == length) { 103 break; 104 } 105 106 int nextSlashIndex = indexOfSlash(pathChars, index, true); 107 108 String element = new String(pathChars, index, nextSlashIndex - index); 109 index = nextSlashIndex; 110 111 // Ignore "." 112 if (".".equals(element)) { 113 continue; 114 } 115 116 // Backtrack ".." 117 if ("..".equals(element)) { 118 if (level == 0) { 119 if (isAbsolutePath) { 120 throw new IllegalStateException(path); 121 } else { 122 buf.append("..").append(PATH_SPLIT); 123 } 124 } else { 125 buf.setLength(pathChars[--level]); 126 } 127 128 continue; 129 } 130 131 pathChars[level++] = (char) buf.length(); 132 buf.append(element).append(PATH_SPLIT); 133 } 134 135 // remove the last "/" 136 if (buf.length() > 0) { 137 if (!endsWithSlash || removeTrailingSlash) { 138 buf.setLength(buf.length() - 1); 139 } 140 } 141 142 return buf.toString(); 143 } 144 145 146 private static int indexOfSlash(char[] chars, int beginIndex, boolean slash) { 147 int i = beginIndex; 148 149 for (; i < chars.length; i++) { 150 char ch = chars[i]; 151 152 if (slash) { 153 if (ch == PATH_SPLIT.charAt(0) || ch == '\\') { 154 break; // if a slash 155 } 156 } else { 157 if (ch != PATH_SPLIT.charAt(0) && ch != '\\') { 158 break; // if not a slash 159 } 160 } 161 } 162 163 return i; 164 } 165 166 167 protected static final String contentType = "application/json; charset=utf-8"; 168 169 public static void writeDefaultBlockedJson(HttpServletResponse resp, Map map) throws IOException { 170 resp.setStatus(200); 171 resp.setContentType(contentType); 172 try (PrintWriter out = resp.getWriter()) { 173 out.print(JsonKit.toJson(map)); 174 } 175 } 176 177 178 public static void writeDefaultBlockedPage(HttpServletResponse resp) throws IOException { 179 resp.setStatus(200); 180 try (PrintWriter out = resp.getWriter()) { 181 out.print("Blocked by Sentinel (flow limiting) in Jboot"); 182 } 183 } 184 185 186 public static void blockRequest(HttpServletRequest request, HttpServletResponse response) { 187 StringBuffer url = request.getRequestURL(); 188 189 if ("GET".equals(request.getMethod()) && StringUtil.isNotBlank(request.getQueryString())) { 190 url.append("?").append(request.getQueryString()); 191 } 192 193 SentinelConfig config = SentinelConfig.get(); 194 195 try { 196 if (StringUtil.isNotBlank(config.getRequestBlockPage())) { 197 String redirectUrl = config.getRequestBlockPage() + "?http_referer=" + url.toString(); 198 response.sendRedirect(redirectUrl); 199 } else if (config.getRequestBlockJsonMap() != null && !config.getRequestBlockJsonMap().isEmpty()) { 200 writeDefaultBlockedJson(response, config.getRequestBlockJsonMap()); 201 } else { 202 writeDefaultBlockedPage(response); 203 } 204 } catch (IOException ex) { 205 LogKit.error(ex.toString(), ex); 206 } 207 } 208 209}