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

com.alibaba.nacos.embedded.web.server.NacosConfigHttpHandler Maven / Gradle / Ivy

/*
 * 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 com.alibaba.nacos.embedded.web.server;

import static java.nio.charset.Charset.forName;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.common.utils.MD5Utils;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

/**
 * . Nacos Config {@link HttpHandler} which only supports request parameters :
 * 
    *
  • {@link #DATA_ID_PARAM_NAME}
  • *
  • {@link #GROUP_ID_PARAM_NAME}
  • *
  • {@link #CONTENT_PARAM_NAME}
  • *
* * @author Mercy * @since 0.1.0 */ public class NacosConfigHttpHandler implements HttpHandler { public static final String DATA_ID_PARAM_NAME = "dataId"; public static final String GROUP_ID_PARAM_NAME = "group"; public static final String CONTENT_PARAM_NAME = "content"; private static final Object LOCK = new Object(); private final Logger logger = LoggerFactory.getLogger(getClass()); private Map contentCache = new HashMap(); private Map longPollingMap = new HashMap(); private ScheduledExecutorService scheduledExecutorService; private volatile boolean isRunning; public void init() { isRunning = true; final int maxWaitInSecond = 3; scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); scheduledExecutorService.scheduleWithFixedDelay(new Runnable() { @Override public void run() { while (isRunning) { synchronized (LOCK) { Set keySet = new HashSet(longPollingMap.keySet()); for (String contentKey : keySet) { LongPolling longPolling = longPollingMap.get(contentKey); if (longPolling == null) { continue; } if (System.currentTimeMillis() - longPolling.date .getTime() > (maxWaitInSecond * 1000L)) { try { write(longPolling.httpExchange, ""); } catch (IOException e) { logger.error( "Polling task encountered an exception, contentKey: " + contentKey, e); } removeLongPolling(longPolling.httpExchange); } } } } } }, maxWaitInSecond, maxWaitInSecond, TimeUnit.SECONDS); } @Override public void handle(HttpExchange httpExchange) throws IOException { String method = httpExchange.getRequestMethod(); if ("GET".equals(method)) { handleGetConfig(httpExchange); } else if ("POST".equals(method)) { String queryString = StreamUtils.copyToString(httpExchange.getRequestBody(), forName("UTF-8")); Map params = parseParams(queryString); String listeningConfigs = params.get("Listening-Configs"); if (listeningConfigs != null) { handleLongPolling(httpExchange, listeningConfigs); } else { handlePublishConfig(httpExchange, params); } } else if ("DELETE".equals(method)) { handleRemoveConfig(httpExchange); } } /** * . Handle {@link ConfigService#publishConfig(String, String, String)} * * @param httpExchange {@link HttpExchange} * @throws IOException IO error */ private void handlePublishConfig(HttpExchange httpExchange, Map params) throws IOException { cacheConfig(params); notifyLongPolling(params); write(httpExchange, "true"); } private void notifyLongPolling(Map params) throws IOException { String contentKey = createContentKey(params); synchronized (LOCK) { LongPolling longPolling = longPollingMap.get(contentKey); if (longPolling != null) { String dataId = params.get(DATA_ID_PARAM_NAME); String groupId = params.get(GROUP_ID_PARAM_NAME); String longPollingResult = createLongPollingResult(dataId, groupId); removeLongPolling(longPolling.httpExchange); write(longPolling.httpExchange, longPollingResult); } } } private void removeLongPolling(HttpExchange httpExchange) { Set keySet = new HashSet(longPollingMap.keySet()); for (String key : keySet) { LongPolling longPolling = longPollingMap.get(key); if (longPolling == null) { continue; } if (longPolling.httpExchange.equals(httpExchange)) { longPollingMap.remove(key); } } } private String createLongPollingResult(String dataId, String groupId) throws IOException { String sb = dataId + Constants.WORD_SEPARATOR + groupId + Constants.LINE_SEPARATOR; return URLEncoder.encode(sb, "UTF-8"); } private String createLongPollingResult(List dataIdList, List groupIdList) throws IOException { StringBuilder sb = new StringBuilder(); int size = dataIdList.size(); for (int i = 0; i < size; i++) { sb.append(dataIdList.get(i)); sb.append(Constants.WORD_SEPARATOR); sb.append(groupIdList.get(i)); sb.append(Constants.LINE_SEPARATOR); } return URLEncoder.encode(sb.toString(), "UTF-8"); } private void handleLongPolling(HttpExchange httpExchange, String listeningConfigs) throws IOException { // @see ClientWorker.checkUpdateDataIds listeningConfigs = URLDecoder.decode(listeningConfigs, "UTF-8"); List changeDataIdList = new ArrayList(); List changeGroupIdList = new ArrayList(); List contentKeyList = new ArrayList(); String[] lines = listeningConfigs.split(Constants.LINE_SEPARATOR); for (String line : lines) { parseLine(changeDataIdList, changeGroupIdList, contentKeyList, line); } if (!changeDataIdList.isEmpty()) { String longPollingResult = createLongPollingResult(changeDataIdList, changeGroupIdList); write(httpExchange, longPollingResult); return; } synchronized (LOCK) { for (String contentKey : contentKeyList) { longPollingMap.put(contentKey, new LongPolling(httpExchange)); } } } private void parseLine(List changeDataIdList, List changeGroupIdList, List contentKeyList, String line) { String[] arr = line.split(Constants.WORD_SEPARATOR, 3); if (arr.length < 3) { logger.warn("Listening-Configs is wrong format, line: {}", line); return; } String dataId = arr[0]; String groupId = arr[1]; String md5 = arr[2]; String contentKey = createContentKey(dataId, groupId); String content = contentCache.get(contentKey); if (content != null) { if (!md5.equals(MD5Utils.md5Hex(content, "UTF-8"))) { changeDataIdList.add(dataId); changeGroupIdList.add(groupId); return; } } contentKeyList.add(contentKey); } public void cacheConfig(Map params) { String content = params.get(CONTENT_PARAM_NAME); String key = createContentKey(params); contentCache.put(key, content); } /** * Handle {@link ConfigService#getConfig(String, String, long)}. * * @param httpExchange {@link HttpExchange} * @throws IOException IO error */ private void handleGetConfig(HttpExchange httpExchange) throws IOException { URI requestUri = httpExchange.getRequestURI(); String queryString = requestUri.getQuery(); Map params = parseParams(queryString); String key = createContentKey(params); String content = contentCache.get(key); write(httpExchange, content); } /** * Handle {@link ConfigService#removeConfig(String, String)}. * * @param httpExchange {@link HttpExchange} * @throws IOException IO error */ private void handleRemoveConfig(HttpExchange httpExchange) throws IOException { URI requestUri = httpExchange.getRequestURI(); String queryString = requestUri.getQuery(); Map params = parseParams(queryString); String key = createContentKey(params); contentCache.remove(key); write(httpExchange, "OK"); } private String createContentKey(Map params) { String dataId = params.get(DATA_ID_PARAM_NAME); String groupId = params.get(GROUP_ID_PARAM_NAME); return createContentKey(dataId, groupId); } private String createContentKey(String dataId, String groupId) { return dataId + " | " + groupId; } private void write(HttpExchange httpExchange, String content) throws IOException { if (content != null) { OutputStream outputStream = httpExchange.getResponseBody(); httpExchange.sendResponseHeaders(200, content.length()); StreamUtils.copy(URLDecoder.decode(content, "UTF-8"), forName("UTF-8"), outputStream); } httpExchange.close(); } private Map parseParams(String queryString) { Map params = new HashMap(); String[] parts = StringUtils.delimitedListToStringArray(queryString, "&"); for (String part : parts) { if (StringUtils.isEmpty(part)) { continue; } String[] nameAndValue = StringUtils.split(part, "="); if (nameAndValue.length != 2) { continue; } params.put(StringUtils.trimAllWhitespace(nameAndValue[0]), StringUtils.trimAllWhitespace(nameAndValue[1])); } return params; } public void destroy() { isRunning = false; if (scheduledExecutorService != null) { scheduledExecutorService.shutdown(); } } private static class LongPolling { private HttpExchange httpExchange; private Date date; LongPolling(HttpExchange httpExchange) { this.httpExchange = httpExchange; this.date = new Date(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy